@marlinjai/mail-contract 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -145,39 +145,341 @@ var API_VERSION_PREFIX = "/v1";
145
145
  var HEALTH_PATH = "/healthz";
146
146
 
147
147
  // src/workspace.ts
148
- import { z as z8 } from "zod";
148
+ import { z as z9 } from "zod";
149
149
 
150
150
  // src/automations.ts
151
- import { z as z7 } from "zod";
151
+ import { z as z8 } from "zod";
152
152
 
153
153
  // src/templates.ts
154
+ import { z as z4 } from "zod";
155
+
156
+ // src/insertions.ts
154
157
  import { z as z3 } from "zod";
158
+
159
+ // src/unsubscribe.ts
160
+ var UNSUBSCRIBE_PATH_PREFIX = "/u/";
161
+ var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
162
+ var RESERVED_MERGE_FIELDS = {
163
+ /** From the recipient's merge values, else the contact. Supports a fallback. */
164
+ first_name: { source: "contact", fallback: true, required_for_broadcast: false },
165
+ last_name: { source: "contact", fallback: true, required_for_broadcast: false },
166
+ email: { source: "contact", fallback: false, required_for_broadcast: false },
167
+ /** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
168
+ unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
169
+ };
170
+ var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
171
+ function findMergeFields(html) {
172
+ const uses = [];
173
+ for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
174
+ uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
175
+ }
176
+ return uses;
177
+ }
178
+ function missingRequiredMergeFields(html) {
179
+ const used = new Set(findMergeFields(html).map((u) => u.name));
180
+ return Object.keys(RESERVED_MERGE_FIELDS).filter(
181
+ (name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
182
+ );
183
+ }
184
+
185
+ // src/insertions.ts
186
+ function ownEntry(record, key) {
187
+ return record != null && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
188
+ }
189
+ var MAX_INSERTIONS = 20;
190
+ var MAX_INSERTION_CHOICES = 30;
191
+ var MAX_INSERTION_TEXT_LENGTH = 2e3;
192
+ var NAME = /^[a-z][a-z0-9_]*$/;
193
+ var InsertionName = z3.string().max(64).regex(NAME, "a merge-field name: lowercase letters, digits and underscores, starting with a letter");
194
+ var INSERTION_TEXT_SLOTS = ["filled", "otherwise"];
195
+ var InsertionChoiceKey = z3.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");
196
+ var InsertionText = z3.string().max(MAX_INSERTION_TEXT_LENGTH);
197
+ var Insertion = z3.object({
198
+ by: InsertionName,
199
+ choices: z3.record(InsertionChoiceKey, InsertionText).refine((c) => Object.keys(c).length <= MAX_INSERTION_CHOICES, `at most ${MAX_INSERTION_CHOICES} choices`),
200
+ filled: InsertionText.nullable(),
201
+ otherwise: InsertionText
202
+ });
203
+ var Insertions = z3.record(InsertionName, Insertion).refine((m) => Object.keys(m).length <= MAX_INSERTIONS, `at most ${MAX_INSERTIONS} insertions`);
204
+ var TranslationInsertion = z3.object({
205
+ choices: z3.record(InsertionChoiceKey, InsertionText.nullable()),
206
+ filled: InsertionText.nullable(),
207
+ otherwise: InsertionText.nullable()
208
+ });
209
+ var TranslationInsertions = z3.record(InsertionName, TranslationInsertion);
210
+ var INSERTION_PROBLEM_REASONS = [
211
+ /** A reserved merge field (`first_name`, `email`, ...) cannot be an insertion's name. */
212
+ "insertion_name",
213
+ /** `by` is `unsubscribe_url` or another insertion's name. */
214
+ "insertion_by",
215
+ /** A text places an insertion: texts hold merge fields only, one level. */
216
+ "insertion_text",
217
+ /** `{{opening|...}}`: an insertion's `otherwise` is its fallback, in every language. */
218
+ "insertion_with_fallback",
219
+ /** A language version names an insertion or a choice the main language does not have. */
220
+ "unknown_translation_insertion"
221
+ ];
222
+ function insertionTokens(text, insertions) {
223
+ const out = [];
224
+ for (const match of text.matchAll(MERGE_FIELD_PATTERN)) {
225
+ const name = match[1];
226
+ if (Object.prototype.hasOwnProperty.call(insertions, name)) {
227
+ out.push({ name, fallback: match[2] === void 0 ? null : match[2] });
228
+ }
229
+ }
230
+ return out;
231
+ }
232
+ function eachText(ins) {
233
+ const texts = [];
234
+ for (const [key, text] of Object.entries(ins.choices)) if (text !== null) texts.push([`choices.${key}`, text]);
235
+ if (ins.filled !== null) texts.push(["filled", ins.filled]);
236
+ if (ins.otherwise !== null) texts.push(["otherwise", ins.otherwise]);
237
+ return texts;
238
+ }
239
+ function insertionProblems(insertions) {
240
+ const problems = [];
241
+ for (const [name, ins] of Object.entries(insertions)) {
242
+ if (Object.prototype.hasOwnProperty.call(RESERVED_MERGE_FIELDS, name)) {
243
+ problems.push({ reason: "insertion_name", path: name, message: `"${name}" is a merge field the service fills; name the insertion something else` });
244
+ }
245
+ if (ins.by === "unsubscribe_url" || Object.prototype.hasOwnProperty.call(insertions, ins.by)) {
246
+ problems.push({ reason: "insertion_by", path: `${name}.by`, message: `"${ins.by}" cannot choose an insertion: use a value the client or the contact provides` });
247
+ }
248
+ for (const [where, text] of eachText(ins)) {
249
+ if (insertionTokens(text, insertions).length > 0) {
250
+ problems.push({ reason: "insertion_text", path: `${name}.${where}`, message: "an insertion text can hold merge fields, not other insertions" });
251
+ }
252
+ }
253
+ }
254
+ return problems;
255
+ }
256
+ function translationInsertionProblems(main, translation) {
257
+ const problems = [];
258
+ for (const [name, ins] of Object.entries(translation)) {
259
+ const def = ownEntry(main, name);
260
+ if (!def) {
261
+ problems.push({ reason: "unknown_translation_insertion", path: name, message: `the main language has no insertion "${name}"` });
262
+ continue;
263
+ }
264
+ for (const key of Object.keys(ins.choices)) {
265
+ if (!Object.prototype.hasOwnProperty.call(def.choices, key)) {
266
+ problems.push({ reason: "unknown_translation_insertion", path: `${name}.choices.${key}`, message: `"${name}" has no choice "${key}" in the main language` });
267
+ }
268
+ }
269
+ for (const [where, text] of eachText(ins)) {
270
+ if (insertionTokens(text, main).length > 0) {
271
+ problems.push({ reason: "insertion_text", path: `${name}.${where}`, message: "an insertion text can hold merge fields, not other insertions" });
272
+ }
273
+ }
274
+ }
275
+ return problems;
276
+ }
277
+ function placementProblems(texts, insertions) {
278
+ const problems = [];
279
+ for (const [where, text] of Object.entries(texts)) {
280
+ if (!text) continue;
281
+ for (const use of insertionTokens(text, insertions)) {
282
+ if (use.fallback !== null) {
283
+ problems.push({
284
+ reason: "insertion_with_fallback",
285
+ path: where,
286
+ message: `{{${use.name}|...}}: an insertion takes no fallback, its "otherwise" text is the fallback`
287
+ });
288
+ }
289
+ }
290
+ }
291
+ return problems;
292
+ }
293
+ function unplacedInsertions(texts, insertions) {
294
+ const placed = /* @__PURE__ */ new Set();
295
+ for (const text of texts) if (text) for (const use of insertionTokens(text, insertions)) placed.add(use.name);
296
+ return Object.keys(insertions).filter((name) => !placed.has(name));
297
+ }
298
+ function insertionCoverage(main, translation) {
299
+ const missing = [];
300
+ for (const [name, def] of Object.entries(main)) {
301
+ const t = ownEntry(translation, name);
302
+ for (const key of Object.keys(def.choices)) {
303
+ const text = ownEntry(t?.choices, key);
304
+ if (typeof text !== "string") missing.push({ insertion: name, choice: key });
305
+ }
306
+ if (def.filled !== null && typeof t?.filled !== "string") missing.push({ insertion: name, choice: "filled" });
307
+ if (typeof t?.otherwise !== "string") missing.push({ insertion: name, choice: "otherwise" });
308
+ }
309
+ return missing;
310
+ }
311
+ function alignTranslationInsertions(main, translation) {
312
+ const out = {};
313
+ for (const [name, def] of Object.entries(main)) {
314
+ const t = ownEntry(translation, name);
315
+ const choices = {};
316
+ for (const key of Object.keys(def.choices)) choices[key] = ownEntry(t?.choices, key) ?? null;
317
+ out[name] = { choices, filled: def.filled === null ? null : t?.filled ?? null, otherwise: t?.otherwise ?? null };
318
+ }
319
+ return out;
320
+ }
321
+ function languageInsertions(main, translation) {
322
+ if (insertionCoverage(main, translation).length > 0) return null;
323
+ const out = {};
324
+ for (const [name, def] of Object.entries(main)) {
325
+ const t = ownEntry(translation, name);
326
+ const choices = {};
327
+ for (const key of Object.keys(def.choices)) choices[key] = ownEntry(t.choices, key);
328
+ out[name] = { by: def.by, choices, filled: def.filled === null ? null : t.filled, otherwise: t.otherwise };
329
+ }
330
+ return out;
331
+ }
332
+ function insertionInputs(insertions) {
333
+ const out = {};
334
+ for (const [name, def] of Object.entries(insertions)) {
335
+ const entry = ownEntry(out, def.by) ?? (out[def.by] = { insertions: [], keys: [], filled: false });
336
+ entry.insertions.push(name);
337
+ for (const key of Object.keys(def.choices)) if (!entry.keys.includes(key)) entry.keys.push(key);
338
+ if (def.filled !== null) entry.filled = true;
339
+ }
340
+ return out;
341
+ }
342
+ function insertionValue(raw) {
343
+ if (typeof raw === "string") return raw.trim() === "" ? null : raw.trim();
344
+ if (typeof raw === "number" && Number.isFinite(raw)) return String(raw);
345
+ if (raw === true) return "true";
346
+ return null;
347
+ }
348
+ var InsertionChoice = z3.object({
349
+ value: z3.string().nullable(),
350
+ chosen: z3.string(),
351
+ matched: z3.boolean()
352
+ });
353
+ var InsertionChoices = z3.record(InsertionName, InsertionChoice);
354
+ function chooseInsertion(insertion, raw) {
355
+ const value = insertionValue(raw);
356
+ if (value !== null) {
357
+ const key = value.toLowerCase();
358
+ if (Object.prototype.hasOwnProperty.call(insertion.choices, key)) {
359
+ return { value, chosen: key, matched: true, text: insertion.choices[key] };
360
+ }
361
+ if (insertion.filled !== null) return { value, chosen: "filled", matched: true, text: insertion.filled };
362
+ return { value, chosen: "otherwise", matched: false, text: insertion.otherwise };
363
+ }
364
+ return { value: null, chosen: "otherwise", matched: true, text: insertion.otherwise };
365
+ }
366
+ function resolveInsertions(insertions, raw) {
367
+ const out = {};
368
+ for (const [name, def] of Object.entries(insertions)) out[name] = chooseInsertion(def, raw(def.by));
369
+ return out;
370
+ }
371
+ function insertionChoices(resolved) {
372
+ const out = {};
373
+ for (const [name, r] of Object.entries(resolved)) out[name] = { value: r.value, chosen: r.chosen, matched: r.matched };
374
+ return out;
375
+ }
376
+ var ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
377
+ function escapeMergeHtml(value) {
378
+ return value.replace(/[&<>"']/g, (ch) => ESCAPES[ch]);
379
+ }
380
+ function pattern() {
381
+ return new RegExp(MERGE_FIELD_PATTERN.source, "g");
382
+ }
383
+ function insertionHtml(text, p) {
384
+ let out = "";
385
+ let last = 0;
386
+ for (const match of text.matchAll(pattern())) {
387
+ out += escapeMergeHtml(text.slice(last, match.index));
388
+ out += escapeMergeHtml(p.value(match[1], match[2]));
389
+ last = match.index + match[0].length;
390
+ }
391
+ out += escapeMergeHtml(text.slice(last));
392
+ return out.trim() === "" ? "" : out.replace(/\r\n|\r|\n/g, "<br>");
393
+ }
394
+ function insertionPlain(text, p) {
395
+ return text.replace(pattern(), (_m, name, fallback) => p.value(name, fallback));
396
+ }
397
+ function tokenHtml(name, fallback, p) {
398
+ const ins = ownEntry(p.insertions, name);
399
+ if (ins) return insertionHtml(ins.text, p);
400
+ return escapeMergeHtml(p.value(name, fallback));
401
+ }
402
+ var FIELD_ONLY_PARAGRAPH = new RegExp(
403
+ 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>`,
404
+ "g"
405
+ );
406
+ var TEXT_BLOCK_ROW = /<tr>\s*<td\b[^>]*\bclass="[^"]*\bel-text\b[^"]*"[^>]*>([\s\S]*?)<\/td>\s*<\/tr>/g;
407
+ function hasVisibleContent(html) {
408
+ if (/<(img|hr|video|svg|input|iframe|table)\b/i.test(html)) return true;
409
+ const text = html.replace(/<!--[\s\S]*?-->/g, "").replace(/<[^>]*>/g, "").replace(/&nbsp;|&#160;| /g, "").trim();
410
+ return text !== "";
411
+ }
412
+ function dropEmptyParagraphs(html, p) {
413
+ return html.replace(FIELD_ONLY_PARAGRAPH, (whole, _tag, _attrs, content) => {
414
+ const tokens = [...content.matchAll(pattern())];
415
+ if (tokens.length === 0) return whole;
416
+ return tokens.every((m) => tokenHtml(m[1], m[2], p).trim() === "") ? "" : whole;
417
+ });
418
+ }
419
+ function personalizeHtml(html, p) {
420
+ let out = html.replace(TEXT_BLOCK_ROW, (whole, inner) => {
421
+ if (!pattern().test(inner)) return whole;
422
+ const kept = dropEmptyParagraphs(inner, p);
423
+ const filled = kept.replace(pattern(), (_m, name, fallback) => tokenHtml(name, fallback, p));
424
+ return hasVisibleContent(filled) ? whole : "";
425
+ });
426
+ out = dropEmptyParagraphs(out, p);
427
+ return out.replace(pattern(), (_m, name, fallback) => tokenHtml(name, fallback, p));
428
+ }
429
+ var CONTROL = new RegExp("[\\u0000-\\u001f\\u007f]+", "g");
430
+ function personalizeText(text, p) {
431
+ return text.replace(pattern(), (_m, name, fallback) => {
432
+ const ins = ownEntry(p.insertions, name);
433
+ return ins ? insertionPlain(ins.text, p) : p.value(name, fallback);
434
+ }).replace(CONTROL, " ").replace(/ {2,}/g, " ").trim();
435
+ }
436
+ function sampleMergeValue(values) {
437
+ return (name, fallback) => {
438
+ const v = ownEntry(values, name);
439
+ const text = typeof v === "string" ? v : typeof v === "number" && Number.isFinite(v) ? String(v) : typeof v === "boolean" ? String(v) : null;
440
+ if (text === null || text.trim() === "") return fallback?.trim() ?? "";
441
+ return text;
442
+ };
443
+ }
444
+
445
+ // src/templates.ts
155
446
  var DOCUMENT_SCHEMA_VERSIONS = ["1.0", "1.1"];
156
- var DocumentSchemaVersion = z3.enum(DOCUMENT_SCHEMA_VERSIONS);
157
- var TemplateDocument = z3.object({
447
+ var DocumentSchemaVersion = z4.enum(DOCUMENT_SCHEMA_VERSIONS);
448
+ var TemplateDocument = z4.object({
158
449
  version: DocumentSchemaVersion,
159
- metadata: z3.record(z3.unknown()),
160
- sections: z3.array(z3.record(z3.unknown()))
450
+ metadata: z4.record(z4.unknown()),
451
+ sections: z4.array(z4.record(z4.unknown()))
161
452
  }).passthrough();
162
453
  var MAX_DOCUMENT_BYTES = 1e6;
163
454
  var MAX_SUBJECT_LENGTH = 998;
164
455
  var MAX_PREHEADER_LENGTH = 500;
165
- var TemplateSubject = z3.string().max(MAX_SUBJECT_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
166
- var TemplatePreheader = z3.string().max(MAX_PREHEADER_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
456
+ var TemplateSubject = z4.string().max(MAX_SUBJECT_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
457
+ var TemplatePreheader = z4.string().max(MAX_PREHEADER_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
167
458
  var TEMPLATE_LANGUAGE_STATUSES = ["draft", "ready"];
168
- var TemplateLanguageStatus = z3.enum(TEMPLATE_LANGUAGE_STATUSES);
169
- var TemplateTranslation = z3.object({
459
+ var TemplateLanguageStatus = z4.enum(TEMPLATE_LANGUAGE_STATUSES);
460
+ var TemplateTranslation = z4.object({
170
461
  subject: TemplateSubject.nullable(),
171
462
  preheader: TemplatePreheader.nullable(),
172
463
  document: TemplateDocument,
173
- status: TemplateLanguageStatus
464
+ status: TemplateLanguageStatus,
465
+ /**
466
+ * This language's texts for the main language's insertions
467
+ * (docs/plans/2026-09-25-translated-insertions.md). The service always
468
+ * returns it, aligned to the main language's structure (a text not written
469
+ * yet is null). In a save, leaving it out keeps the stored texts, so a
470
+ * client that does not know insertions never erases them. A `ready` version
471
+ * needs every text: marking one ready without is `ready_needs_insertions`,
472
+ * and a save that leaves a ready version without one (the main language
473
+ * added a choice) sets it back to `draft`.
474
+ */
475
+ insertions: TranslationInsertions.optional()
174
476
  });
175
477
  var MAX_TEMPLATE_TRANSLATIONS = 19;
176
- var TemplateTranslations = z3.record(LocaleTag, TemplateTranslation).refine((t) => Object.keys(t).length <= MAX_TEMPLATE_TRANSLATIONS, `at most ${MAX_TEMPLATE_TRANSLATIONS} language versions`);
177
- var Template = z3.object({
478
+ var TemplateTranslations = z4.record(LocaleTag, TemplateTranslation).refine((t) => Object.keys(t).length <= MAX_TEMPLATE_TRANSLATIONS, `at most ${MAX_TEMPLATE_TRANSLATIONS} language versions`);
479
+ var Template = z4.object({
178
480
  id: Id,
179
- name: z3.string().min(1).max(200),
180
- description: z3.string().max(2e3).nullable(),
481
+ name: z4.string().min(1).max(200),
482
+ description: z4.string().max(2e3).nullable(),
181
483
  /**
182
484
  * The main language: what `subject`, `preheader` and `document` are written
183
485
  * in, and what every recipient without a ready version of their own gets.
@@ -194,114 +496,132 @@ var Template = z3.object({
194
496
  * lists is kept, but no send uses it until the language is listed again.
195
497
  */
196
498
  translations: TemplateTranslations,
499
+ /**
500
+ * The main language's insertions: named sentences chosen per recipient by
501
+ * one merge value, placed as `{{name}}` (see `insertions.ts`). Their
502
+ * structure is the main language's; every other language holds its own
503
+ * texts in `translations[tag].insertions`.
504
+ */
505
+ insertions: Insertions,
197
506
  /** Increments on every saved change, of any language. */
198
- version: z3.number().int().min(1),
199
- thumbnail_url: z3.string().url().nullable(),
507
+ version: z4.number().int().min(1),
508
+ thumbnail_url: z4.string().url().nullable(),
200
509
  archived_at: Timestamp.nullable(),
201
510
  created_at: Timestamp,
202
511
  updated_at: Timestamp
203
512
  });
204
- var TemplateSummary = Template.omit({ document: true, translations: true }).extend({
205
- languages: z3.record(LocaleTag, TemplateLanguageStatus)
513
+ var TemplateSummary = Template.omit({ document: true, translations: true, insertions: true }).extend({
514
+ languages: z4.record(LocaleTag, TemplateLanguageStatus),
515
+ /** The names of the template's insertions, sorted alphabetically. */
516
+ insertion_names: z4.array(z4.string())
206
517
  });
207
- var TemplateCreate = z3.object({
208
- name: z3.string().min(1).max(200),
209
- description: z3.string().max(2e3).optional(),
518
+ var TemplateCreate = z4.object({
519
+ name: z4.string().min(1).max(200),
520
+ description: z4.string().max(2e3).optional(),
210
521
  locale: LocaleTag.optional(),
211
522
  subject: TemplateSubject.nullable().optional(),
212
523
  preheader: TemplatePreheader.nullable().optional(),
213
524
  document: TemplateDocument,
214
- translations: TemplateTranslations.optional()
525
+ translations: TemplateTranslations.optional(),
526
+ insertions: Insertions.optional()
215
527
  });
216
- var TemplateUpdate = z3.object({
217
- base_version: z3.number().int().min(1),
218
- name: z3.string().min(1).max(200).optional(),
219
- description: z3.string().max(2e3).nullable().optional(),
528
+ var TemplateUpdate = z4.object({
529
+ base_version: z4.number().int().min(1),
530
+ name: z4.string().min(1).max(200).optional(),
531
+ description: z4.string().max(2e3).nullable().optional(),
220
532
  locale: LocaleTag.optional(),
221
533
  subject: TemplateSubject.nullable().optional(),
222
534
  preheader: TemplatePreheader.nullable().optional(),
223
535
  document: TemplateDocument.optional(),
224
- translations: z3.record(LocaleTag, TemplateTranslation.nullable()).optional(),
225
- archived: z3.boolean().optional()
536
+ translations: z4.record(LocaleTag, TemplateTranslation.nullable()).optional(),
537
+ /**
538
+ * The main language's insertions, whole: what is left out is removed, from
539
+ * every language in the same save. A rename is a removal and an addition,
540
+ * so an editor carries each language's texts to the new name in the same
541
+ * save (it sends those languages in `translations`).
542
+ */
543
+ insertions: Insertions.optional(),
544
+ archived: z4.boolean().optional()
226
545
  }).refine(
227
- (v) => v.name !== void 0 || v.description !== void 0 || v.locale !== void 0 || v.subject !== void 0 || v.preheader !== void 0 || v.document !== void 0 || v.translations !== void 0 || v.archived !== void 0,
546
+ (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,
228
547
  "at least one field besides base_version"
229
548
  );
230
- var SavedSectionBody = z3.record(z3.unknown());
549
+ var SavedSectionBody = z4.record(z4.unknown());
231
550
  var MAX_SAVED_SECTIONS = 50;
232
- var SavedSection = z3.object({
551
+ var SavedSection = z4.object({
233
552
  id: Id,
234
- name: z3.string().min(1).max(200),
235
- description: z3.string().max(2e3).nullable(),
553
+ name: z4.string().min(1).max(200),
554
+ description: z4.string().max(2e3).nullable(),
236
555
  section: SavedSectionBody,
237
- created_by: z3.string().nullable(),
556
+ created_by: z4.string().nullable(),
238
557
  created_at: Timestamp,
239
558
  updated_at: Timestamp
240
559
  });
241
- var SavedSectionCreate = z3.object({
242
- name: z3.string().min(1).max(200),
243
- description: z3.string().max(2e3).optional(),
560
+ var SavedSectionCreate = z4.object({
561
+ name: z4.string().min(1).max(200),
562
+ description: z4.string().max(2e3).optional(),
244
563
  section: SavedSectionBody
245
564
  });
246
- var SavedSectionUpdate = z3.object({
247
- name: z3.string().min(1).max(200).optional(),
248
- description: z3.string().max(2e3).nullable().optional(),
565
+ var SavedSectionUpdate = z4.object({
566
+ name: z4.string().min(1).max(200).optional(),
567
+ description: z4.string().max(2e3).nullable().optional(),
249
568
  section: SavedSectionBody.optional()
250
569
  }).refine((v) => v.name !== void 0 || v.description !== void 0 || v.section !== void 0, "at least one field");
251
570
  var TemplateListQuery = PageQuery.extend({
252
- archived: z3.enum(["true", "false"]).optional()
571
+ archived: z4.enum(["true", "false"]).optional()
253
572
  });
254
- var TemplateVersion = z3.object({
573
+ var TemplateVersion = z4.object({
255
574
  template_id: Id,
256
- version: z3.number().int().min(1),
575
+ version: z4.number().int().min(1),
257
576
  locale: LocaleTag,
258
577
  subject: TemplateSubject.nullable(),
259
578
  preheader: TemplatePreheader.nullable(),
260
579
  document: TemplateDocument,
261
580
  translations: TemplateTranslations,
262
- created_by: z3.string().nullable(),
581
+ insertions: Insertions,
582
+ created_by: z4.string().nullable(),
263
583
  created_at: Timestamp
264
584
  });
265
- var TemplateVersionParams = z3.object({
585
+ var TemplateVersionParams = z4.object({
266
586
  id: Id,
267
- version: z3.coerce.number().int().min(1)
587
+ version: z4.coerce.number().int().min(1)
268
588
  });
269
- var CompileMessage = z3.object({
270
- message: z3.string().min(1),
589
+ var CompileMessage = z4.object({
590
+ message: z4.string().min(1),
271
591
  /** Where in the document or the MJML the message points, when known. */
272
- path: z3.string().optional(),
273
- line: z3.number().int().min(1).optional()
592
+ path: z4.string().optional(),
593
+ line: z4.number().int().min(1).optional()
274
594
  });
275
- var CompileResult = z3.object({
276
- mjml: z3.string(),
277
- html: z3.string(),
278
- warnings: z3.array(CompileMessage),
279
- errors: z3.array(CompileMessage)
595
+ var CompileResult = z4.object({
596
+ mjml: z4.string(),
597
+ html: z4.string(),
598
+ warnings: z4.array(CompileMessage),
599
+ errors: z4.array(CompileMessage)
280
600
  });
281
- var TemplateCompileRequest = z3.object({
282
- version: z3.number().int().min(1).optional(),
601
+ var TemplateCompileRequest = z4.object({
602
+ version: z4.number().int().min(1).optional(),
283
603
  locale: LocaleTag.optional()
284
604
  });
285
- var CompileRequest = z3.object({
605
+ var CompileRequest = z4.object({
286
606
  document: TemplateDocument
287
607
  });
288
608
  var ASSET_CONTENT_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
289
- var AssetContentType = z3.enum(ASSET_CONTENT_TYPES);
609
+ var AssetContentType = z4.enum(ASSET_CONTENT_TYPES);
290
610
  var MAX_ASSET_BYTES = 10 * 1024 * 1024;
291
- var Asset = z3.object({
611
+ var Asset = z4.object({
292
612
  id: Id,
293
- url: z3.string().url(),
613
+ url: z4.string().url(),
294
614
  content_type: AssetContentType,
295
- size_bytes: z3.number().int().min(1).max(MAX_ASSET_BYTES),
296
- width: z3.number().int().min(1).nullable(),
297
- height: z3.number().int().min(1).nullable(),
298
- filename: z3.string().min(1).max(255),
615
+ size_bytes: z4.number().int().min(1).max(MAX_ASSET_BYTES),
616
+ width: z4.number().int().min(1).nullable(),
617
+ height: z4.number().int().min(1).nullable(),
618
+ filename: z4.string().min(1).max(255),
299
619
  created_at: Timestamp
300
620
  });
301
621
  var ASSET_UPLOAD_FIELD = "file";
302
622
  var MAX_IMPORT_URL_LENGTH = 2048;
303
- var AssetImport = z3.object({
304
- url: z3.string().max(MAX_IMPORT_URL_LENGTH).url().refine((u) => {
623
+ var AssetImport = z4.object({
624
+ url: z4.string().max(MAX_IMPORT_URL_LENGTH).url().refine((u) => {
305
625
  try {
306
626
  const url = new URL(u);
307
627
  if (url.protocol === "https:") return true;
@@ -311,7 +631,7 @@ var AssetImport = z3.object({
311
631
  }
312
632
  }, "must be https (http only for localhost)"),
313
633
  /** The stored file name; derived from the address when omitted. */
314
- filename: z3.string().min(1).max(255).optional()
634
+ filename: z4.string().min(1).max(255).optional()
315
635
  });
316
636
  var MAX_MJML_IMPORT_BYTES = 512 * 1024;
317
637
  var MAX_MJML_DEPTH = 32;
@@ -326,56 +646,56 @@ var MJML_IMPORT_REFUSALS = [
326
646
  "too_complex",
327
647
  "invalid_document"
328
648
  ];
329
- var ImportWarning = z3.object({
330
- severity: z3.enum(["info", "warning"]),
649
+ var ImportWarning = z4.object({
650
+ severity: z4.enum(["info", "warning"]),
331
651
  /** Stable kind, e.g. `kept_as_html`, `unknown_component`, `remote_image_not_imported`. */
332
- code: z3.string().min(1).max(64),
652
+ code: z4.string().min(1).max(64),
333
653
  /** Where in the source, e.g. `mj-body > mj-section[2] > mj-column[1] > mj-social[1]`. */
334
- path: z3.string(),
335
- line: z3.number().int().min(1).optional(),
336
- message: z3.string().min(1),
337
- fragment: z3.string().optional()
654
+ path: z4.string(),
655
+ line: z4.number().int().min(1).optional(),
656
+ message: z4.string().min(1),
657
+ fragment: z4.string().optional()
338
658
  });
339
- var RemoteImage = z3.object({
340
- url: z3.string().min(1),
659
+ var RemoteImage = z4.object({
660
+ url: z4.string().min(1),
341
661
  /** The element and attribute it is loaded from, e.g. `<img src>`. */
342
- where: z3.string()
662
+ where: z4.string()
343
663
  });
344
- var ImportedAsset = z3.object({
345
- source_url: z3.string().min(1),
664
+ var ImportedAsset = z4.object({
665
+ source_url: z4.string().min(1),
346
666
  asset_id: Id,
347
- url: z3.string().url()
667
+ url: z4.string().url()
348
668
  });
349
- var mjmlSource = z3.string().min(1, "paste or upload the MJML");
350
- var TemplateImportPreviewRequest = z3.object({ mjml: mjmlSource });
351
- var TemplateImportPreview = z3.object({
669
+ var mjmlSource = z4.string().min(1, "paste or upload the MJML");
670
+ var TemplateImportPreviewRequest = z4.object({ mjml: mjmlSource });
671
+ var TemplateImportPreview = z4.object({
352
672
  document: TemplateDocument,
353
- warnings: z3.array(ImportWarning),
673
+ warnings: z4.array(ImportWarning),
354
674
  /** The document compiled under the workspace's asset policy, exactly as a send would. */
355
675
  compiled: CompileResult,
356
676
  /** Images loaded from outside the service; under `service_only` each is also a compile error. */
357
- remote_images: z3.array(RemoteImage),
358
- asset_policy: z3.enum(["any", "service_only"])
677
+ remote_images: z4.array(RemoteImage),
678
+ asset_policy: z4.enum(["any", "service_only"])
359
679
  });
360
- var TemplateImport = z3.object({
361
- name: z3.string().min(1).max(200),
362
- description: z3.string().max(2e3).optional(),
680
+ var TemplateImport = z4.object({
681
+ name: z4.string().min(1).max(200),
682
+ description: z4.string().max(2e3).optional(),
363
683
  mjml: mjmlSource,
364
- import_remote_assets: z3.boolean().optional()
684
+ import_remote_assets: z4.boolean().optional()
365
685
  });
366
- var TemplateImportResult = z3.object({
686
+ var TemplateImportResult = z4.object({
367
687
  template: Template,
368
- warnings: z3.array(ImportWarning),
369
- imported_assets: z3.array(ImportedAsset)
688
+ warnings: z4.array(ImportWarning),
689
+ imported_assets: z4.array(ImportedAsset)
370
690
  });
371
691
  var EXPORT_FORMATS = ["mjml", "html"];
372
- var ExportFormat = z3.enum(EXPORT_FORMATS);
373
- var TemplateExportQuery = z3.object({
692
+ var ExportFormat = z4.enum(EXPORT_FORMATS);
693
+ var TemplateExportQuery = z4.object({
374
694
  format: ExportFormat,
375
- version: z3.coerce.number().int().min(1).optional(),
695
+ version: z4.coerce.number().int().min(1).optional(),
376
696
  locale: LocaleTag.optional()
377
697
  });
378
- var MailingExportQuery = z3.object({ format: ExportFormat });
698
+ var MailingExportQuery = z4.object({ format: ExportFormat });
379
699
  var EXPORT_CONTENT_TYPES = {
380
700
  mjml: "text/plain; charset=utf-8",
381
701
  html: "text/html; charset=utf-8"
@@ -394,7 +714,7 @@ function formatExportWarningsHeader(messages) {
394
714
  function parseExportWarningsHeader(value) {
395
715
  if (!value) return [];
396
716
  try {
397
- const parsed = z3.array(CompileMessage).safeParse(JSON.parse(decodeURIComponent(value)));
717
+ const parsed = z4.array(CompileMessage).safeParse(JSON.parse(decodeURIComponent(value)));
398
718
  return parsed.success ? parsed.data : [];
399
719
  } catch {
400
720
  return [];
@@ -406,32 +726,32 @@ function exportFilename(name, format) {
406
726
  }
407
727
 
408
728
  // src/platform.ts
409
- import { z as z6 } from "zod";
729
+ import { z as z7 } from "zod";
410
730
 
411
731
  // src/mailings.ts
412
- import { z as z5 } from "zod";
732
+ import { z as z6 } from "zod";
413
733
 
414
734
  // src/ab-test.ts
415
- import { z as z4 } from "zod";
416
- var AbVariant = z4.object({
417
- key: z4.string().regex(/^[a-z]$/, "a single lowercase letter"),
418
- subject: z4.string().min(1).max(998).optional(),
735
+ import { z as z5 } from "zod";
736
+ var AbVariant = z5.object({
737
+ key: z5.string().regex(/^[a-z]$/, "a single lowercase letter"),
738
+ subject: z5.string().min(1).max(998).optional(),
419
739
  /** Replaces the mailing's document for this variant. */
420
- document: z4.record(z4.unknown()).optional(),
740
+ document: z5.record(z5.unknown()).optional(),
421
741
  /**
422
742
  * Keeps the document this variant key already has in the mailing's current
423
743
  * test, so a test can be changed without sending every document again. The
424
744
  * service refuses it (`validation_failed`) when that variant has none.
425
745
  */
426
- keep_document: z4.literal(true).optional()
746
+ keep_document: z5.literal(true).optional()
427
747
  });
428
748
  var AB_WINNER_METRICS = ["opens", "clicks", "manual"];
429
- var AbTestConfig = z4.object({
430
- variants: z4.array(AbVariant).min(2).max(5),
749
+ var AbTestConfig = z5.object({
750
+ variants: z5.array(AbVariant).min(2).max(5),
431
751
  /** Share of recipients in the test group, split evenly across variants; the rest get the winner. */
432
- test_fraction: z4.number().gt(0).lte(1),
433
- winner_metric: z4.enum(AB_WINNER_METRICS),
434
- decide_after_minutes: z4.number().int().min(15).max(10080).optional()
752
+ test_fraction: z5.number().gt(0).lte(1),
753
+ winner_metric: z5.enum(AB_WINNER_METRICS),
754
+ decide_after_minutes: z5.number().int().min(15).max(10080).optional()
435
755
  }).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), {
436
756
  message: "every variant changes the subject, the document or both",
437
757
  path: ["variants"]
@@ -443,19 +763,19 @@ var AbTestConfig = z4.object({
443
763
  path: ["decide_after_minutes"]
444
764
  });
445
765
  var AB_TEST_STATUSES = ["pending", "testing", "awaiting_pick", "decided"];
446
- var AbTestState = z4.object({
447
- variants: z4.array(z4.object({ key: z4.string(), subject: z4.string().nullable(), has_document: z4.boolean() })),
448
- test_fraction: z4.number(),
449
- winner_metric: z4.enum(AB_WINNER_METRICS),
450
- decide_after_minutes: z4.number().int().nullable(),
451
- status: z4.enum(AB_TEST_STATUSES),
766
+ var AbTestState = z5.object({
767
+ variants: z5.array(z5.object({ key: z5.string(), subject: z5.string().nullable(), has_document: z5.boolean() })),
768
+ test_fraction: z5.number(),
769
+ winner_metric: z5.enum(AB_WINNER_METRICS),
770
+ decide_after_minutes: z5.number().int().nullable(),
771
+ status: z5.enum(AB_TEST_STATUSES),
452
772
  /** When the metric decides; null for manual or before the start. */
453
773
  decide_at: Timestamp.nullable(),
454
- winner: z4.string().nullable(),
455
- decided_by: z4.enum(["metric", "manual"]).nullable(),
774
+ winner: z5.string().nullable(),
775
+ decided_by: z5.enum(["metric", "manual"]).nullable(),
456
776
  decided_at: Timestamp.nullable()
457
777
  });
458
- var AbWinnerRequest = z4.object({ variant: z4.string().regex(/^[a-z]$/) });
778
+ var AbWinnerRequest = z5.object({ variant: z5.string().regex(/^[a-z]$/) });
459
779
  function testGroupSize(total, fraction, variants) {
460
780
  return Math.min(total, Math.max(variants, Math.ceil(total * fraction)));
461
781
  }
@@ -470,9 +790,9 @@ var MAILING_STATUSES = [
470
790
  "partially_failed",
471
791
  "cancelled"
472
792
  ];
473
- var MailingStatus = z5.enum(MAILING_STATUSES);
793
+ var MailingStatus = z6.enum(MAILING_STATUSES);
474
794
  var MAILING_ACTIONS = ["send", "schedule", "unschedule", "pause", "resume", "cancel", "retry-failed"];
475
- var MailingAction = z5.enum(MAILING_ACTIONS);
795
+ var MailingAction = z6.enum(MAILING_ACTIONS);
476
796
  var MAILING_TRANSITIONS = {
477
797
  draft: ["send", "schedule", "cancel"],
478
798
  scheduled: ["send", "schedule", "unschedule", "cancel"],
@@ -487,23 +807,23 @@ function canTransition(status, action) {
487
807
  }
488
808
  var EDITABLE_MAILING_STATUSES = ["draft", "scheduled"];
489
809
  var TERMINAL_MAILING_STATUSES = ["sent", "cancelled"];
490
- var MailingMetadata = z5.record(z5.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");
491
- var MailingCounts = z5.object({
492
- total: z5.number().int().min(0),
493
- queued: z5.number().int().min(0),
494
- sending: z5.number().int().min(0),
495
- sent: z5.number().int().min(0),
496
- failed: z5.number().int().min(0),
497
- skipped: z5.number().int().min(0)
810
+ var MailingMetadata = z6.record(z6.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");
811
+ var MailingCounts = z6.object({
812
+ total: z6.number().int().min(0),
813
+ queued: z6.number().int().min(0),
814
+ sending: z6.number().int().min(0),
815
+ sent: z6.number().int().min(0),
816
+ failed: z6.number().int().min(0),
817
+ skipped: z6.number().int().min(0)
498
818
  });
499
819
  var MAILING_KINDS = ["broadcast", "automation", "notification"];
500
- var MailingKind = z5.enum(MAILING_KINDS);
501
- var Mailing = z5.object({
820
+ var MailingKind = z6.enum(MAILING_KINDS);
821
+ var Mailing = z6.object({
502
822
  id: Id,
503
823
  kind: MailingKind,
504
- name: z5.string().max(200).nullable(),
505
- subject: z5.string().min(1).max(998),
506
- preheader: z5.string().max(500).nullable(),
824
+ name: z6.string().max(200).nullable(),
825
+ subject: z6.string().min(1).max(998),
826
+ preheader: z6.string().max(500).nullable(),
507
827
  /**
508
828
  * The template the document was taken from, if any. The snapshot is what is
509
829
  * sent in the main language; the template's other language versions are
@@ -548,15 +868,15 @@ var Mailing = z5.object({
548
868
  * recipients refused as dead addresses in one run (see the provider's
549
869
  * `rejections.anomaly`). Resuming clears it and starts a new run.
550
870
  */
551
- pause_reason: z5.string().nullable().default(null),
871
+ pause_reason: z6.string().nullable().default(null),
552
872
  created_at: Timestamp,
553
873
  updated_at: Timestamp
554
874
  });
555
875
  var MailingSummary = Mailing.omit({ document: true });
556
876
  var MailingContent = {
557
- name: z5.string().max(200).optional(),
558
- subject: z5.string().min(1).max(998),
559
- preheader: z5.string().max(500).optional(),
877
+ name: z6.string().max(200).optional(),
878
+ subject: z6.string().min(1).max(998),
879
+ preheader: z6.string().max(500).optional(),
560
880
  /** See `Mailing.send_locale`. */
561
881
  send_locale: LocaleTag.nullable().optional(),
562
882
  /**
@@ -569,7 +889,7 @@ var MailingContent = {
569
889
  provider_id: Id,
570
890
  metadata: MailingMetadata.optional()
571
891
  };
572
- var MailingCreate = z5.object({
892
+ var MailingCreate = z6.object({
573
893
  ...MailingContent,
574
894
  subject: MailingContent.subject.optional(),
575
895
  document: TemplateDocument.optional(),
@@ -581,10 +901,10 @@ var MailingCreate = z5.object({
581
901
  message: "a mailing made from a document needs a subject",
582
902
  path: ["subject"]
583
903
  });
584
- var MailingUpdate = z5.object({
585
- name: z5.string().max(200).nullable().optional(),
904
+ var MailingUpdate = z6.object({
905
+ name: z6.string().max(200).nullable().optional(),
586
906
  subject: MailingContent.subject.optional(),
587
- preheader: z5.string().max(500).nullable().optional(),
907
+ preheader: z6.string().max(500).nullable().optional(),
588
908
  /** Null turns a draft into a letter, which it can only be with at most one recipient. */
589
909
  topic: Slug.nullable().optional(),
590
910
  send_locale: LocaleTag.nullable().optional(),
@@ -598,30 +918,30 @@ var MailingListQuery = PageQuery.extend({
598
918
  kind: MailingKind.optional(),
599
919
  automation_id: Id.optional()
600
920
  });
601
- var MailingSendRequest = z5.object({}).strict();
602
- var MailingRetryFailedRequest = z5.object({
921
+ var MailingSendRequest = z6.object({}).strict();
922
+ var MailingRetryFailedRequest = z6.object({
603
923
  /**
604
924
  * Also requeue recipients whose outcome is unknown after a crash. They may
605
925
  * already have received the message, so this is a human's decision, never a
606
926
  * default.
607
927
  */
608
- include_outcome_unknown: z5.boolean().optional()
928
+ include_outcome_unknown: z6.boolean().optional()
609
929
  }).strict();
610
- var MailingActionRequest = z5.object({}).strict();
611
- var MailingTestRequest = z5.object({
930
+ var MailingActionRequest = z6.object({}).strict();
931
+ var MailingTestRequest = z6.object({
612
932
  to: Email,
613
933
  merge: Properties.optional(),
614
934
  locale: LocaleTag.optional()
615
935
  });
616
- var MailingTestResult = z5.object({
936
+ var MailingTestResult = z6.object({
617
937
  message_id: Id,
618
- provider_message_id: z5.string().nullable()
938
+ provider_message_id: z6.string().nullable()
619
939
  });
620
940
  var RECIPIENT_STATUSES = ["queued", "sending", "sent", "failed", "skipped"];
621
- var RecipientStatus = z5.enum(RECIPIENT_STATUSES);
941
+ var RecipientStatus = z6.enum(RECIPIENT_STATUSES);
622
942
  var SKIP_REASONS = ["suppressed", "not_subscribed", "contact_erased", "cancelled", "outcome_unknown"];
623
- var SkipReason = z5.enum(SKIP_REASONS);
624
- var Recipient = z5.object({
943
+ var SkipReason = z6.enum(SKIP_REASONS);
944
+ var Recipient = z6.object({
625
945
  id: Id,
626
946
  mailing_id: Id,
627
947
  contact_id: Id.nullable(),
@@ -629,75 +949,98 @@ var Recipient = z5.object({
629
949
  merge: Properties,
630
950
  status: RecipientStatus,
631
951
  skip_reason: SkipReason.nullable(),
632
- attempts: z5.number().int().min(0),
952
+ attempts: z6.number().int().min(0),
633
953
  message_id: Id.nullable(),
634
- last_error: z5.string().nullable(),
954
+ last_error: z6.string().nullable(),
635
955
  created_at: Timestamp,
636
956
  updated_at: Timestamp
637
957
  });
638
958
  var MAX_RECIPIENTS_PER_BATCH = 1e3;
639
959
  var MAX_LETTER_RECIPIENTS = 1;
640
960
  var LETTER_RECIPIENT_REFUSAL = "letter_has_one_recipient";
641
- var RecipientInput = z5.object({
961
+ var RecipientInput = z6.object({
642
962
  contact_id: Id.optional(),
643
- external_id: z5.string().min(1).max(255).optional(),
963
+ external_id: z6.string().min(1).max(255).optional(),
644
964
  email: Email.optional(),
645
965
  merge: Properties.optional()
646
966
  }).refine((v) => v.contact_id !== void 0 || v.external_id !== void 0 || v.email !== void 0, {
647
967
  message: "contact_id, external_id or email is required",
648
968
  path: ["email"]
649
969
  });
650
- var RecipientBatch = z5.object({
651
- recipients: z5.array(RecipientInput).min(1).max(MAX_RECIPIENTS_PER_BATCH)
970
+ var RecipientBatch = z6.object({
971
+ recipients: z6.array(RecipientInput).min(1).max(MAX_RECIPIENTS_PER_BATCH)
652
972
  });
653
973
  var RECIPIENT_REJECTIONS = ["unknown_contact", "duplicate_in_batch"];
654
- var RecipientBatchResult = z5.object({
974
+ var RecipientBatchResult = z6.object({
655
975
  /** Newly queued. */
656
- added: z5.number().int().min(0),
976
+ added: z6.number().int().min(0),
657
977
  /** Already on the mailing from an earlier batch: left unchanged. */
658
- already_present: z5.number().int().min(0),
978
+ already_present: z6.number().int().min(0),
659
979
  /** Items not added, by their index in the request. */
660
- rejected: z5.array(z5.object({ index: z5.number().int().min(0), reason: z5.enum(RECIPIENT_REJECTIONS) }))
980
+ rejected: z6.array(z6.object({ index: z6.number().int().min(0), reason: z6.enum(RECIPIENT_REJECTIONS) }))
661
981
  });
662
982
  var RecipientListQuery = PageQuery.extend({
663
983
  status: RecipientStatus.optional(),
664
984
  skip_reason: SkipReason.optional()
665
985
  });
666
986
  var MESSAGE_OUTCOMES = ["sent", "failed"];
667
- var MessageOutcome = z5.enum(MESSAGE_OUTCOMES);
668
- var MessageSummary = z5.object({
987
+ var MessageOutcome = z6.enum(MESSAGE_OUTCOMES);
988
+ var MessageSummary = z6.object({
669
989
  id: Id,
670
990
  mailing_id: Id.nullable(),
671
991
  recipient_id: Id.nullable(),
672
992
  contact_id: Id.nullable(),
673
993
  to: Email,
674
- subject: z5.string(),
994
+ subject: z6.string(),
675
995
  provider_id: Id,
676
- provider_message_id: z5.string().nullable(),
996
+ provider_message_id: z6.string().nullable(),
677
997
  outcome: MessageOutcome,
678
- error: z5.string().nullable(),
679
- is_test: z5.boolean(),
680
- recipient_count: z5.number().int().min(1),
998
+ error: z6.string().nullable(),
999
+ is_test: z6.boolean(),
1000
+ recipient_count: z6.number().int().min(1),
681
1001
  /** The language version the message was sent in; null for messages archived before languages existed. */
682
1002
  locale: LocaleTag.nullable(),
1003
+ /**
1004
+ * The choice each of the template's insertions made for this recipient
1005
+ * (`{ opening: { value: "venue", chosen: "venue", matched: true } }`);
1006
+ * `matched: false` is a filled value no choice took, sent with the
1007
+ * "otherwise" text. Null when the message had no insertions.
1008
+ */
1009
+ insertions: InsertionChoices.nullable(),
683
1010
  created_at: Timestamp
684
1011
  });
685
- var Message = MessageSummary.extend({ html: z5.string() });
686
- var MailingLanguageStatus = z5.enum(["main", "ready", "draft", "not_a_workspace_language"]);
687
- var MailingLanguage = z5.object({
1012
+ var Message = MessageSummary.extend({ html: z6.string() });
1013
+ var MailingLanguageStatus = z6.enum(["main", "ready", "draft", "not_a_workspace_language"]);
1014
+ var MailingLanguage = z6.object({
688
1015
  locale: LocaleTag,
689
1016
  status: MailingLanguageStatus,
690
1017
  /** Queued recipients who will get this version. */
691
- planned: z5.number().int().min(0),
1018
+ planned: z6.number().int().min(0),
692
1019
  /** Messages already sent in this language (tests excluded). */
693
- sent: z5.number().int().min(0)
1020
+ sent: z6.number().int().min(0)
694
1021
  });
695
- var MailingLanguageReport = z5.object({
1022
+ var MailingLanguageReport = z6.object({
696
1023
  main_locale: LocaleTag,
697
1024
  send_locale: LocaleTag.nullable(),
698
- languages: z5.array(MailingLanguage),
699
- fallback: z5.array(z5.object({ wanted: z5.string(), planned: z5.number().int().min(1) })),
700
- no_language: z5.number().int().min(0)
1025
+ languages: z6.array(MailingLanguage),
1026
+ fallback: z6.array(z6.object({ wanted: z6.string(), planned: z6.number().int().min(1) })),
1027
+ no_language: z6.number().int().min(0),
1028
+ /**
1029
+ * Values that no choice of an insertion takes, so the "otherwise" text goes
1030
+ * out (docs/plans/2026-09-25-translated-insertions.md, decision 3): per
1031
+ * insertion and value, the queued recipients who send it (`planned`) and the
1032
+ * messages that went out with it (`sent`). A client whose form offers the
1033
+ * template's keys never produces one; a non-empty list means the client and
1034
+ * the template have drifted apart.
1035
+ */
1036
+ insertion_misses: z6.array(
1037
+ z6.object({
1038
+ insertion: z6.string(),
1039
+ value: z6.string(),
1040
+ planned: z6.number().int().min(0),
1041
+ sent: z6.number().int().min(0)
1042
+ })
1043
+ )
701
1044
  });
702
1045
  var MessageListQuery = PageQuery.extend({
703
1046
  mailing_id: Id.optional(),
@@ -705,26 +1048,26 @@ var MessageListQuery = PageQuery.extend({
705
1048
  });
706
1049
 
707
1050
  // src/platform.ts
708
- var Tag = z6.object({
1051
+ var Tag = z7.object({
709
1052
  id: Id,
710
1053
  slug: Slug,
711
- name: z6.string().min(1).max(120),
712
- contact_count: z6.number().int().min(0),
1054
+ name: z7.string().min(1).max(120),
1055
+ contact_count: z7.number().int().min(0),
713
1056
  created_at: Timestamp
714
1057
  });
715
- var TagCreate = z6.object({ slug: Slug, name: z6.string().min(1).max(120) });
716
- var TagAssignment = z6.object({
717
- contact_ids: z6.array(Id).min(1).max(1e3)
1058
+ var TagCreate = z7.object({ slug: Slug, name: z7.string().min(1).max(120) });
1059
+ var TagAssignment = z7.object({
1060
+ contact_ids: z7.array(Id).min(1).max(1e3)
718
1061
  });
719
1062
  var CONTACT_PROPERTY_TYPES = ["string", "number", "boolean", "date"];
720
- var ContactPropertyType = z6.enum(CONTACT_PROPERTY_TYPES);
721
- var ContactPropertyKey = z6.string().regex(/^[A-Za-z0-9_.-]{1,64}$/, "letters, digits, dot, underscore and hyphen, 1 to 64");
722
- var ContactPropertyDefinition = z6.object({
1063
+ var ContactPropertyType = z7.enum(CONTACT_PROPERTY_TYPES);
1064
+ var ContactPropertyKey = z7.string().regex(/^[A-Za-z0-9_.-]{1,64}$/, "letters, digits, dot, underscore and hyphen, 1 to 64");
1065
+ var ContactPropertyDefinition = z7.object({
723
1066
  key: ContactPropertyKey,
724
- label: z6.string().min(1).max(120),
1067
+ label: z7.string().min(1).max(120),
725
1068
  type: ContactPropertyType
726
1069
  });
727
- var ContactPropertyParams = z6.object({ key: ContactPropertyKey });
1070
+ var ContactPropertyParams = z7.object({ key: ContactPropertyKey });
728
1071
  var FILTER_OPERATORS = [
729
1072
  "eq",
730
1073
  "neq",
@@ -740,8 +1083,8 @@ var FILTER_OPERATORS = [
740
1083
  "in",
741
1084
  "not_in"
742
1085
  ];
743
- var FilterOperator = z6.enum(FILTER_OPERATORS);
744
- var FilterField = z6.string().regex(
1086
+ var FilterOperator = z7.enum(FILTER_OPERATORS);
1087
+ var FilterField = z7.string().regex(
745
1088
  /^(email|first_name|last_name|locale|created_at|tag|topic|engagement:(opened|clicked)|property:[A-Za-z0-9_.-]{1,64})$/,
746
1089
  "unknown filter field"
747
1090
  );
@@ -757,18 +1100,18 @@ var FILTER_FIELD_OPERATORS = {
757
1100
  property: FILTER_OPERATORS
758
1101
  };
759
1102
  var MAX_FILTER_DEPTH = 5;
760
- var FilterCondition = z6.object({ field: FilterField, op: FilterOperator, value: JsonValue.optional() }).strict().refine((c) => (c.op === "exists" || c.op === "not_exists") === (c.value === void 0), {
1103
+ var FilterCondition = z7.object({ field: FilterField, op: FilterOperator, value: JsonValue.optional() }).strict().refine((c) => (c.op === "exists" || c.op === "not_exists") === (c.value === void 0), {
761
1104
  message: "exists and not_exists take no value; every other operator needs one",
762
1105
  path: ["value"]
763
1106
  }).refine((c) => c.op !== "in" && c.op !== "not_in" || Array.isArray(c.value), {
764
1107
  message: "in and not_in take an array",
765
1108
  path: ["value"]
766
1109
  });
767
- var SegmentFilter = z6.lazy(
768
- () => z6.union([
769
- z6.object({ and: z6.array(SegmentFilter).min(1).max(50) }).strict(),
770
- z6.object({ or: z6.array(SegmentFilter).min(1).max(50) }).strict(),
771
- z6.object({ not: SegmentFilter }).strict(),
1110
+ var SegmentFilter = z7.lazy(
1111
+ () => z7.union([
1112
+ z7.object({ and: z7.array(SegmentFilter).min(1).max(50) }).strict(),
1113
+ z7.object({ or: z7.array(SegmentFilter).min(1).max(50) }).strict(),
1114
+ z7.object({ not: SegmentFilter }).strict(),
772
1115
  FilterCondition
773
1116
  ])
774
1117
  );
@@ -856,42 +1199,42 @@ function untypedValueProblem(c, every) {
856
1199
  if (orderOp) return every(isFilterNumber, "numbers (define the property to compare dates)");
857
1200
  return every((v) => isScalar(v) || v === null, "strings, numbers, booleans or null");
858
1201
  }
859
- var Segment = z6.object({
1202
+ var Segment = z7.object({
860
1203
  id: Id,
861
- name: z6.string().min(1).max(120),
1204
+ name: z7.string().min(1).max(120),
862
1205
  filter: SegmentFilter,
863
1206
  /** Computed on read. */
864
- contact_count: z6.number().int().min(0),
1207
+ contact_count: z7.number().int().min(0),
865
1208
  created_at: Timestamp,
866
1209
  updated_at: Timestamp
867
1210
  });
868
- var SegmentCreate = z6.object({ name: z6.string().min(1).max(120), filter: BoundedSegmentFilter });
869
- var SegmentPreviewRequest = z6.object({ filter: BoundedSegmentFilter });
870
- var SegmentPreview = z6.object({
871
- contact_count: z6.number().int().min(0),
1211
+ var SegmentCreate = z7.object({ name: z7.string().min(1).max(120), filter: BoundedSegmentFilter });
1212
+ var SegmentPreviewRequest = z7.object({ filter: BoundedSegmentFilter });
1213
+ var SegmentPreview = z7.object({
1214
+ contact_count: z7.number().int().min(0),
872
1215
  /** Up to five matching contacts, for a human to sanity-check the filter. */
873
- sample: z6.array(z6.object({ id: Id, email: Email })).max(5)
1216
+ sample: z7.array(z7.object({ id: Id, email: Email })).max(5)
874
1217
  });
875
- var MailingAudienceFromSegment = z6.object({ segment_id: Id });
1218
+ var MailingAudienceFromSegment = z7.object({ segment_id: Id });
876
1219
  var SIGNUP_FORM_FIELDS = ["first_name", "last_name"];
877
1220
  var HOSTED_PAGE_LOCALES = ["en", "de", "it", "fr", "es"];
878
- var SignupFormTranslation = z6.object({
879
- title: z6.string().min(1).max(120),
880
- consent_text: z6.string().min(1).max(2e3)
1221
+ var SignupFormTranslation = z7.object({
1222
+ title: z7.string().min(1).max(120),
1223
+ consent_text: z7.string().min(1).max(2e3)
881
1224
  });
882
- var SignupForm = z6.object({
1225
+ var SignupForm = z7.object({
883
1226
  id: Id,
884
- name: z6.string().min(1).max(120),
1227
+ name: z7.string().min(1).max(120),
885
1228
  /** The heading of the hosted page. */
886
- title: z6.string().min(1).max(120),
1229
+ title: z7.string().min(1).max(120),
887
1230
  /** What the person agrees to, shown above the button and recorded with the confirmation. */
888
- consent_text: z6.string().min(1).max(2e3),
889
- translations: z6.record(SignupFormTranslation),
890
- topics: z6.array(Slug).min(1),
891
- tags: z6.array(Slug),
1231
+ consent_text: z7.string().min(1).max(2e3),
1232
+ translations: z7.record(SignupFormTranslation),
1233
+ topics: z7.array(Slug).min(1),
1234
+ tags: z7.array(Slug),
892
1235
  /** Fields shown besides email. */
893
- fields: z6.array(z6.enum(SIGNUP_FORM_FIELDS)),
894
- double_opt_in: z6.literal(true),
1236
+ fields: z7.array(z7.enum(SIGNUP_FORM_FIELDS)),
1237
+ double_opt_in: z7.literal(true),
895
1238
  /** The provider the confirmation mail is sent through. */
896
1239
  provider_id: Id,
897
1240
  /**
@@ -900,11 +1243,11 @@ var SignupForm = z6.object({
900
1243
  */
901
1244
  confirmation_template_id: Id.nullable(),
902
1245
  /** Where the person lands after confirming. Null shows the hosted confirmation page. */
903
- redirect_url: z6.string().url().nullable(),
1246
+ redirect_url: z7.string().url().nullable(),
904
1247
  /** Origins allowed to embed the form and call the submission route from a browser. */
905
- allowed_origins: z6.array(z6.string().url()).max(20),
1248
+ allowed_origins: z7.array(z7.string().url()).max(20),
906
1249
  /** Bumped on every change; recorded with each confirmation. */
907
- version: z6.number().int().min(1),
1250
+ version: z7.number().int().min(1),
908
1251
  created_at: Timestamp,
909
1252
  updated_at: Timestamp
910
1253
  });
@@ -919,56 +1262,56 @@ var SignupFormCreate = SignupForm.omit({
919
1262
  tags: SignupForm.shape.tags.optional(),
920
1263
  fields: SignupForm.shape.fields.optional(),
921
1264
  confirmation_template_id: Id.nullable().optional(),
922
- redirect_url: z6.string().url().nullable().optional(),
1265
+ redirect_url: z7.string().url().nullable().optional(),
923
1266
  allowed_origins: SignupForm.shape.allowed_origins.optional()
924
1267
  });
925
- var SignupFormEmbed = z6.object({
926
- hosted_url: z6.string().url(),
1268
+ var SignupFormEmbed = z7.object({
1269
+ hosted_url: z7.string().url(),
927
1270
  /** A plain HTML form posting to the hosted page; works without JavaScript. */
928
- html: z6.string(),
1271
+ html: z7.string(),
929
1272
  /** A small optional script that adds the time check and submits in place. */
930
- script_url: z6.string().url(),
1273
+ script_url: z7.string().url(),
931
1274
  /** Where a browser fetches a fresh `form_token` from (GET, public, CORS for `allowed_origins`). */
932
- token_url: z6.string().url()
1275
+ token_url: z7.string().url()
933
1276
  });
934
- var SignupSubmission = z6.object({
1277
+ var SignupSubmission = z7.object({
935
1278
  email: Email,
936
- first_name: z6.string().max(200).optional(),
937
- last_name: z6.string().max(200).optional(),
938
- locale: z6.string().min(2).max(35).optional(),
1279
+ first_name: z7.string().max(200).optional(),
1280
+ last_name: z7.string().max(200).optional(),
1281
+ locale: z7.string().min(2).max(35).optional(),
939
1282
  /** Honeypot: must be empty. */
940
- website: z6.string().max(0).optional(),
1283
+ website: z7.string().max(0).optional(),
941
1284
  /**
942
1285
  * The signed render time from the form's token URL: a submission sooner than a
943
1286
  * human could fill the form, or long after, is refused.
944
1287
  */
945
- form_token: z6.string().min(1).max(512)
1288
+ form_token: z7.string().min(1).max(512)
946
1289
  });
947
1290
  var IMPORT_STATUSES = ["uploaded", "validating", "validated", "committing", "completed", "failed", "cancelled"];
948
- var ImportStatus = z6.enum(IMPORT_STATUSES);
1291
+ var ImportStatus = z7.enum(IMPORT_STATUSES);
949
1292
  var IMPORT_TERMINAL_STATUSES = ["completed", "failed", "cancelled"];
950
- var ImportColumnMapping = z6.record(
951
- z6.string().regex(/^(email|external_id|first_name|last_name|locale|property:[A-Za-z0-9_.-]{1,64}|ignore)$/)
1293
+ var ImportColumnMapping = z7.record(
1294
+ z7.string().regex(/^(email|external_id|first_name|last_name|locale|property:[A-Za-z0-9_.-]{1,64}|ignore)$/)
952
1295
  );
953
1296
  var IMPORT_FILE_FIELD = "file";
954
1297
  var MAX_IMPORT_BYTES = 50 * 1024 * 1024;
955
1298
  var MAX_IMPORT_ROWS = 2e5;
956
- var ImportMappingRequest = z6.object({
1299
+ var ImportMappingRequest = z7.object({
957
1300
  /** CSV header -> contact field. Exactly one column maps to `email`. */
958
1301
  mapping: ImportColumnMapping.refine(
959
1302
  (m) => Object.values(m).filter((v) => v === "email").length === 1,
960
1303
  "exactly one column maps to email"
961
1304
  ),
962
- topics: z6.array(Slug).max(100),
963
- tags: z6.array(Slug).max(100),
1305
+ topics: z7.array(Slug).max(100),
1306
+ tags: z7.array(Slug).max(100),
964
1307
  /** Existing contacts get the mapped fields overwritten; false only adds topics, tags and missing fields. */
965
- update_existing: z6.boolean().optional(),
1308
+ update_existing: z7.boolean().optional(),
966
1309
  /** The importer states the people consented; the service records who said so and when. */
967
- consent_confirmed: z6.literal(true)
1310
+ consent_confirmed: z7.literal(true)
968
1311
  });
969
- var ImportCommitRequest = z6.object({ mapping_version: z6.number().int().min(1) });
1312
+ var ImportCommitRequest = z7.object({ mapping_version: z7.number().int().min(1) });
970
1313
  var IMPORT_ROW_OUTCOMES = ["created", "updated", "unchanged", "suppressed", "skipped"];
971
- var ImportRowOutcome = z6.enum(IMPORT_ROW_OUTCOMES);
1314
+ var ImportRowOutcome = z7.enum(IMPORT_ROW_OUTCOMES);
972
1315
  var IMPORT_SKIP_REASONS = [
973
1316
  "missing_email",
974
1317
  "invalid_email",
@@ -977,90 +1320,90 @@ var IMPORT_SKIP_REASONS = [
977
1320
  "external_id_conflict",
978
1321
  "wrong_column_count"
979
1322
  ];
980
- var ImportSkipReason = z6.enum(IMPORT_SKIP_REASONS);
981
- var ImportReport = z6.object({
982
- created: z6.number().int().min(0),
983
- updated: z6.number().int().min(0),
984
- unchanged: z6.number().int().min(0),
985
- suppressed: z6.number().int().min(0),
986
- skipped: z6.number().int().min(0),
987
- skipped_by_reason: z6.record(ImportSkipReason, z6.number().int().min(0)),
1323
+ var ImportSkipReason = z7.enum(IMPORT_SKIP_REASONS);
1324
+ var ImportReport = z7.object({
1325
+ created: z7.number().int().min(0),
1326
+ updated: z7.number().int().min(0),
1327
+ unchanged: z7.number().int().min(0),
1328
+ suppressed: z7.number().int().min(0),
1329
+ skipped: z7.number().int().min(0),
1330
+ skipped_by_reason: z7.record(ImportSkipReason, z7.number().int().min(0)),
988
1331
  /** Rows whose contact was subscribed without some topic, because the address is blocked for it. */
989
- topics_withheld: z6.number().int().min(0)
1332
+ topics_withheld: z7.number().int().min(0)
990
1333
  });
991
- var ImportJob = z6.object({
1334
+ var ImportJob = z7.object({
992
1335
  id: Id,
993
1336
  status: ImportStatus,
994
- file_name: z6.string().max(255).nullable(),
995
- file_bytes: z6.number().int().min(0),
996
- total_rows: z6.number().int().min(0),
1337
+ file_name: z7.string().max(255).nullable(),
1338
+ file_bytes: z7.number().int().min(0),
1339
+ total_rows: z7.number().int().min(0),
997
1340
  /** The header row, as in the file. */
998
- columns: z6.array(z6.string()),
1341
+ columns: z7.array(z7.string()),
999
1342
  /** The first rows, for the mapping screen. */
1000
- sample: z6.array(z6.array(z6.string())).max(5),
1343
+ sample: z7.array(z7.array(z7.string())).max(5),
1001
1344
  /** A mapping guessed from the headers, for the mapping screen to start from. */
1002
1345
  suggested_mapping: ImportColumnMapping,
1003
1346
  mapping: ImportColumnMapping.nullable(),
1004
- mapping_version: z6.number().int().min(0),
1005
- topics: z6.array(Slug),
1006
- tags: z6.array(Slug),
1007
- update_existing: z6.boolean(),
1347
+ mapping_version: z7.number().int().min(0),
1348
+ topics: z7.array(Slug),
1349
+ tags: z7.array(Slug),
1350
+ update_existing: z7.boolean(),
1008
1351
  /** The dry run of the current mapping; null before one finished. */
1009
1352
  dry_run: ImportReport.nullable(),
1010
1353
  /** What the commit has written so far; null before it started. */
1011
1354
  result: ImportReport.nullable(),
1012
1355
  /** Rows the current phase (dry run or commit) has processed. */
1013
- processed_rows: z6.number().int().min(0),
1356
+ processed_rows: z7.number().int().min(0),
1014
1357
  /** Why a `failed` import failed. */
1015
- error: z6.string().nullable(),
1358
+ error: z7.string().nullable(),
1016
1359
  created_at: Timestamp,
1017
1360
  updated_at: Timestamp,
1018
1361
  finished_at: Timestamp.nullable()
1019
1362
  });
1020
- var ImportRow = z6.object({
1363
+ var ImportRow = z7.object({
1021
1364
  /** The line in the file, 1 being the first line after the header. */
1022
- row: z6.number().int().min(1),
1023
- email: z6.string().nullable(),
1365
+ row: z7.number().int().min(1),
1366
+ email: z7.string().nullable(),
1024
1367
  outcome: ImportRowOutcome,
1025
1368
  reason: ImportSkipReason.nullable(),
1026
1369
  /** A human explanation for a skipped row (which value, which column). */
1027
- message: z6.string().nullable(),
1370
+ message: z7.string().nullable(),
1028
1371
  contact_id: Id.nullable()
1029
1372
  });
1030
1373
  var ImportRowListQuery = PageQuery.extend({ outcome: ImportRowOutcome.optional() });
1031
- var MailingScheduleRequest = z6.object({ send_at: Timestamp });
1032
- var TrackingSettings = z6.object({ opens: z6.boolean(), clicks: z6.boolean() });
1033
- var VariantAnalytics = z6.object({
1034
- key: z6.string(),
1035
- sent: z6.number().int().min(0),
1036
- unique_opens: z6.number().int().min(0).nullable(),
1037
- unique_clicks: z6.number().int().min(0).nullable()
1038
- });
1039
- var MailingAnalytics = z6.object({
1374
+ var MailingScheduleRequest = z7.object({ send_at: Timestamp });
1375
+ var TrackingSettings = z7.object({ opens: z7.boolean(), clicks: z7.boolean() });
1376
+ var VariantAnalytics = z7.object({
1377
+ key: z7.string(),
1378
+ sent: z7.number().int().min(0),
1379
+ unique_opens: z7.number().int().min(0).nullable(),
1380
+ unique_clicks: z7.number().int().min(0).nullable()
1381
+ });
1382
+ var MailingAnalytics = z7.object({
1040
1383
  mailing_id: Id,
1041
1384
  counts: MailingCounts,
1042
1385
  /** What was tracked for this mailing, fixed when it started; null before it started. */
1043
1386
  tracking: TrackingSettings.nullable(),
1044
1387
  /** Unique recipients with a human open. Null when opens were not tracked. */
1045
- unique_opens: z6.number().int().min(0).nullable(),
1388
+ unique_opens: z7.number().int().min(0).nullable(),
1046
1389
  /**
1047
1390
  * Recipients whose only opens came from Apple Mail Privacy Protection, which
1048
1391
  * loads every image on delivery: counted apart, never as human opens.
1049
1392
  */
1050
- apple_mpp_opens: z6.number().int().min(0).nullable(),
1393
+ apple_mpp_opens: z7.number().int().min(0).nullable(),
1051
1394
  /** Opens and clicks from security scanners and prefetchers, filtered out of the unique counts. */
1052
- machine_events: z6.number().int().min(0).nullable(),
1053
- unique_clicks: z6.number().int().min(0).nullable(),
1054
- unsubscribes: z6.number().int().min(0),
1055
- bounces: z6.number().int().min(0),
1056
- complaints: z6.number().int().min(0),
1057
- links: z6.array(z6.object({ url: z6.string(), unique_clicks: z6.number().int().min(0) })).nullable(),
1058
- variants: z6.array(VariantAnalytics).nullable()
1395
+ machine_events: z7.number().int().min(0).nullable(),
1396
+ unique_clicks: z7.number().int().min(0).nullable(),
1397
+ unsubscribes: z7.number().int().min(0),
1398
+ bounces: z7.number().int().min(0),
1399
+ complaints: z7.number().int().min(0),
1400
+ links: z7.array(z7.object({ url: z7.string(), unique_clicks: z7.number().int().min(0) })).nullable(),
1401
+ variants: z7.array(VariantAnalytics).nullable()
1059
1402
  });
1060
1403
 
1061
1404
  // src/automations.ts
1062
1405
  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})$/;
1063
- var AutomationFilterField = z7.string().regex(AUTOMATION_FILTER_FIELD_PATTERN, "unknown filter field");
1406
+ var AutomationFilterField = z8.string().regex(AUTOMATION_FILTER_FIELD_PATTERN, "unknown filter field");
1064
1407
  var AUTOMATION_FILTER_FIELD_OPERATORS = {
1065
1408
  ...FILTER_FIELD_OPERATORS,
1066
1409
  segment: ["eq", "neq", "in", "not_in"],
@@ -1070,18 +1413,18 @@ var AUTOMATION_FILTER_FIELD_OPERATORS = {
1070
1413
  step: ["eq", "neq"]
1071
1414
  };
1072
1415
  var automationFilterFamily = filterFieldFamily;
1073
- var AutomationFilterCondition = z7.object({ field: AutomationFilterField, op: FilterOperator, value: JsonValue.optional() }).strict().refine((c) => (c.op === "exists" || c.op === "not_exists") === (c.value === void 0), {
1416
+ var AutomationFilterCondition = z8.object({ field: AutomationFilterField, op: FilterOperator, value: JsonValue.optional() }).strict().refine((c) => (c.op === "exists" || c.op === "not_exists") === (c.value === void 0), {
1074
1417
  message: "exists and not_exists take no value; every other operator needs one",
1075
1418
  path: ["value"]
1076
1419
  }).refine((c) => c.op !== "in" && c.op !== "not_in" || Array.isArray(c.value), {
1077
1420
  message: "in and not_in take an array",
1078
1421
  path: ["value"]
1079
1422
  });
1080
- var AutomationFilter = z7.lazy(
1081
- () => z7.union([
1082
- z7.object({ and: z7.array(AutomationFilter).min(1).max(50) }).strict(),
1083
- z7.object({ or: z7.array(AutomationFilter).min(1).max(50) }).strict(),
1084
- z7.object({ not: AutomationFilter }).strict(),
1423
+ var AutomationFilter = z8.lazy(
1424
+ () => z8.union([
1425
+ z8.object({ and: z8.array(AutomationFilter).min(1).max(50) }).strict(),
1426
+ z8.object({ or: z8.array(AutomationFilter).min(1).max(50) }).strict(),
1427
+ z8.object({ not: AutomationFilter }).strict(),
1085
1428
  AutomationFilterCondition
1086
1429
  ])
1087
1430
  );
@@ -1094,20 +1437,20 @@ function filterConditions(filter) {
1094
1437
  if ("not" in filter) return filterConditions(filter.not);
1095
1438
  return [filter];
1096
1439
  }
1097
- var StepId = z7.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "letters, digits, underscore and hyphen, 1 to 64");
1098
- var TimeOfDay = z7.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "HH:MM in 24 hours");
1099
- var Weekday = z7.number().int().min(0).max(6);
1100
- var TimezoneName = z7.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");
1440
+ var StepId = z8.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "letters, digits, underscore and hyphen, 1 to 64");
1441
+ var TimeOfDay = z8.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "HH:MM in 24 hours");
1442
+ var Weekday = z8.number().int().min(0).max(6);
1443
+ var TimezoneName = z8.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");
1101
1444
  var MAX_DELAY_SECONDS = 365 * 24 * 60 * 60;
1102
- var DateOnly = z7.string().regex(/^\d{4}-\d{2}-\d{2}$/, "a calendar date such as 2026-09-20").refine((v) => {
1445
+ var DateOnly = z8.string().regex(/^\d{4}-\d{2}-\d{2}$/, "a calendar date such as 2026-09-20").refine((v) => {
1103
1446
  const [y, m, d] = v.split("-").map(Number);
1104
1447
  const t = new Date(Date.UTC(y, m - 1, d));
1105
1448
  return t.getUTCFullYear() === y && t.getUTCMonth() + 1 === m && t.getUTCDate() === d;
1106
1449
  }, "a real calendar date");
1107
- var OffsetDays = z7.number().int().min(-365).max(365);
1450
+ var OffsetDays = z8.number().int().min(-365).max(365);
1108
1451
  var DATE_PASSED_ANSWERS = ["skip", "stop", "continue"];
1109
- var DatePassedAnswer = z7.enum(DATE_PASSED_ANSWERS);
1110
- var EventName = z7.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$/, "letters, digits, dot, colon, underscore and hyphen, 1 to 80");
1452
+ var DatePassedAnswer = z8.enum(DATE_PASSED_ANSWERS);
1453
+ var EventName = z8.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$/, "letters, digits, dot, colon, underscore and hyphen, 1 to 80");
1111
1454
  var AUTOMATION_TRIGGER_KINDS = [
1112
1455
  "topic_subscribed",
1113
1456
  "form_confirmed",
@@ -1120,17 +1463,17 @@ var AUTOMATION_TRIGGER_KINDS = [
1120
1463
  "segment_entered",
1121
1464
  "link_clicked"
1122
1465
  ];
1123
- var AutomationTriggerKind = z7.enum(AUTOMATION_TRIGGER_KINDS);
1466
+ var AutomationTriggerKind = z8.enum(AUTOMATION_TRIGGER_KINDS);
1124
1467
  var exclude = { exclude: BoundedAutomationFilter.nullish() };
1125
- var AutomationTrigger = z7.discriminatedUnion("kind", [
1126
- z7.object({ kind: z7.literal("topic_subscribed"), topic: Slug, ...exclude }).strict(),
1127
- z7.object({ kind: z7.literal("form_confirmed"), form_id: Id, ...exclude }).strict(),
1128
- z7.object({ kind: z7.literal("tag_added"), tag: Slug, ...exclude }).strict(),
1468
+ var AutomationTrigger = z8.discriminatedUnion("kind", [
1469
+ z8.object({ kind: z8.literal("topic_subscribed"), topic: Slug, ...exclude }).strict(),
1470
+ z8.object({ kind: z8.literal("form_confirmed"), form_id: Id, ...exclude }).strict(),
1471
+ z8.object({ kind: z8.literal("tag_added"), tag: Slug, ...exclude }).strict(),
1129
1472
  /** `to` given: only a change that lands on this value fires. Absent: any change of the key. */
1130
- z7.object({ kind: z7.literal("property_changed"), key: ContactPropertyKey, to: JsonValue.optional(), ...exclude }).strict(),
1473
+ z8.object({ kind: z8.literal("property_changed"), key: ContactPropertyKey, to: JsonValue.optional(), ...exclude }).strict(),
1131
1474
  /** `where` filters the event's own data, so only `event:<key>` leaves belong in it. */
1132
- z7.object({ kind: z7.literal("event"), name: EventName, where: BoundedAutomationFilter.nullish(), ...exclude }).strict(),
1133
- z7.object({ kind: z7.literal("manual"), ...exclude }).strict(),
1475
+ z8.object({ kind: z8.literal("event"), name: EventName, where: BoundedAutomationFilter.nullish(), ...exclude }).strict(),
1476
+ z8.object({ kind: z8.literal("manual"), ...exclude }).strict(),
1134
1477
  /**
1135
1478
  * Every year on a date the contact carries: a birthday, a joining date, a
1136
1479
  * renewal. `offset_days` moves it ("three days before"), `at` is the wall
@@ -1140,8 +1483,8 @@ var AutomationTrigger = z7.discriminatedUnion("kind", [
1140
1483
  * A 29 February date fires on 1 March in a year that has no 29 February, so
1141
1484
  * nobody is skipped every fourth year.
1142
1485
  */
1143
- z7.object({
1144
- kind: z7.literal("date_anniversary"),
1486
+ z8.object({
1487
+ kind: z8.literal("date_anniversary"),
1145
1488
  key: ContactPropertyKey,
1146
1489
  offset_days: OffsetDays,
1147
1490
  at: TimeOfDay,
@@ -1153,8 +1496,8 @@ var AutomationTrigger = z7.discriminatedUnion("kind", [
1153
1496
  * not ask about a segment or about an event's data: there is no event, and a
1154
1497
  * segment is its own trigger kind.
1155
1498
  */
1156
- z7.object({
1157
- kind: z7.literal("exact_date"),
1499
+ z8.object({
1500
+ kind: z8.literal("exact_date"),
1158
1501
  on: DateOnly,
1159
1502
  at: TimeOfDay,
1160
1503
  where: BoundedAutomationFilter.nullish(),
@@ -1173,7 +1516,7 @@ var AutomationTrigger = z7.discriminatedUnion("kind", [
1173
1516
  * already exists. Entering those people as well is the publish dialog's
1174
1517
  * backfill question, answered by a person rather than by the sweep.
1175
1518
  */
1176
- z7.object({ kind: z7.literal("segment_entered"), segment_id: Id, ...exclude }).strict(),
1519
+ z8.object({ kind: z8.literal("segment_entered"), segment_id: Id, ...exclude }).strict(),
1177
1520
  /**
1178
1521
  * S4. When somebody clicks a link in a mailing that has already gone out.
1179
1522
  *
@@ -1183,7 +1526,7 @@ var AutomationTrigger = z7.discriminatedUnion("kind", [
1183
1526
  * tracking off there is no click to hear about, so the validator refuses it
1184
1527
  * rather than leaving an automation that can never fire.
1185
1528
  */
1186
- z7.object({ kind: z7.literal("link_clicked"), mailing_id: Id, url: z7.string().min(1).max(2048).optional(), ...exclude }).strict()
1529
+ z8.object({ kind: z8.literal("link_clicked"), mailing_id: Id, url: z8.string().min(1).max(2048).optional(), ...exclude }).strict()
1187
1530
  ]);
1188
1531
  var AUTOMATION_DATE_TRIGGER_KINDS = ["date_anniversary", "exact_date"];
1189
1532
  var MAX_TRIGGERS = 10;
@@ -1202,41 +1545,45 @@ var AUTOMATION_STEP_KINDS = [
1202
1545
  "webhook",
1203
1546
  "notify"
1204
1547
  ];
1205
- var AutomationStepKind = z7.enum(AUTOMATION_STEP_KINDS);
1548
+ var AutomationStepKind = z8.enum(AUTOMATION_STEP_KINDS);
1206
1549
  var next = { next: StepId.nullable() };
1207
- var Unchosen = z7.literal("");
1208
- var DraftSlug = z7.union([Slug, Unchosen]);
1209
- var DraftPropertyKey = z7.union([ContactPropertyKey, Unchosen]);
1210
- var label = { label: z7.string().max(120).nullish() };
1211
- var EmailStepLanguage = z7.object({
1212
- subject: z7.string().min(1).max(998),
1213
- preheader: z7.string().max(500).nullable(),
1214
- document: TemplateDocument
1550
+ var Unchosen = z8.literal("");
1551
+ var DraftSlug = z8.union([Slug, Unchosen]);
1552
+ var DraftPropertyKey = z8.union([ContactPropertyKey, Unchosen]);
1553
+ var label = { label: z8.string().max(120).nullish() };
1554
+ var EmailStepLanguage = z8.object({
1555
+ subject: z8.string().min(1).max(998),
1556
+ preheader: z8.string().max(500).nullable(),
1557
+ document: TemplateDocument,
1558
+ /** This language's insertions, its texts over the template's structure (every text present). */
1559
+ insertions: Insertions.nullish()
1215
1560
  }).strict();
1216
- var EmailStep = z7.object({
1217
- kind: z7.literal("email"),
1561
+ var EmailStep = z8.object({
1562
+ kind: z8.literal("email"),
1218
1563
  ...label,
1219
1564
  ...next,
1220
1565
  template_id: Id.nullish(),
1221
1566
  document: TemplateDocument.nullish(),
1222
1567
  /** Empty while the step is being written, or when its template has a subject; else `not_chosen` refuses to publish it. */
1223
- subject: z7.string().max(998),
1224
- preheader: z7.string().max(500).nullish(),
1568
+ subject: z8.string().max(998),
1569
+ preheader: z8.string().max(500).nullish(),
1225
1570
  locale: LocaleTag.nullish(),
1226
- languages: z7.record(LocaleTag, EmailStepLanguage).nullish()
1571
+ languages: z8.record(LocaleTag, EmailStepLanguage).nullish(),
1572
+ /** The main language's insertions, snapshot from the template when the automation is published. */
1573
+ insertions: Insertions.nullish()
1227
1574
  }).strict();
1228
- var DelayStep = z7.object({
1229
- kind: z7.literal("delay"),
1575
+ var DelayStep = z8.object({
1576
+ kind: z8.literal("delay"),
1230
1577
  ...label,
1231
1578
  ...next,
1232
- wait: z7.discriminatedUnion("mode", [
1233
- z7.object({ mode: z7.literal("duration"), seconds: z7.number().int().min(1).max(MAX_DELAY_SECONDS) }).strict(),
1234
- z7.object({ mode: z7.literal("time_of_day"), at: TimeOfDay }).strict(),
1235
- z7.object({ mode: z7.literal("weekday"), weekday: Weekday, at: TimeOfDay }).strict(),
1579
+ wait: z8.discriminatedUnion("mode", [
1580
+ z8.object({ mode: z8.literal("duration"), seconds: z8.number().int().min(1).max(MAX_DELAY_SECONDS) }).strict(),
1581
+ z8.object({ mode: z8.literal("time_of_day"), at: TimeOfDay }).strict(),
1582
+ z8.object({ mode: z8.literal("weekday"), weekday: Weekday, at: TimeOfDay }).strict(),
1236
1583
  /** A fixed day has no next occurrence, so `continue` is not one of its answers. */
1237
- z7.object({ mode: z7.literal("date"), on: DateOnly, at: TimeOfDay, passed: z7.enum(["skip", "stop"]) }).strict(),
1238
- z7.object({
1239
- mode: z7.literal("date_property"),
1584
+ z8.object({ mode: z8.literal("date"), on: DateOnly, at: TimeOfDay, passed: z8.enum(["skip", "stop"]) }).strict(),
1585
+ z8.object({
1586
+ mode: z8.literal("date_property"),
1240
1587
  key: ContactPropertyKey,
1241
1588
  offset_days: OffsetDays,
1242
1589
  at: TimeOfDay,
@@ -1244,8 +1591,8 @@ var DelayStep = z7.object({
1244
1591
  }).strict()
1245
1592
  ])
1246
1593
  }).strict();
1247
- var ConditionStep = z7.object({
1248
- kind: z7.literal("condition"),
1594
+ var ConditionStep = z8.object({
1595
+ kind: z8.literal("condition"),
1249
1596
  ...label,
1250
1597
  filter: BoundedAutomationFilter,
1251
1598
  yes: StepId.nullable(),
@@ -1256,49 +1603,49 @@ var ConditionStep = z7.object({
1256
1603
  * neither half means anything without the other: a window with no answer
1257
1604
  * for the end of it would be a silent decision.
1258
1605
  */
1259
- wait_for: z7.object({ up_to_s: z7.number().int().min(1).max(MAX_DELAY_SECONDS), on_timeout: z7.enum(["yes", "no"]) }).strict().nullish()
1606
+ wait_for: z8.object({ up_to_s: z8.number().int().min(1).max(MAX_DELAY_SECONDS), on_timeout: z8.enum(["yes", "no"]) }).strict().nullish()
1260
1607
  }).strict();
1261
- var SplitBranchKey = z7.string().regex(/^[A-Za-z0-9_-]{1,32}$/, "letters, digits, underscore and hyphen, 1 to 32");
1608
+ var SplitBranchKey = z8.string().regex(/^[A-Za-z0-9_-]{1,32}$/, "letters, digits, underscore and hyphen, 1 to 32");
1262
1609
  var MIN_SPLIT_BRANCHES = 2;
1263
1610
  var MAX_SPLIT_BRANCHES = 4;
1264
- var SplitStep = z7.object({
1265
- kind: z7.literal("split"),
1611
+ var SplitStep = z8.object({
1612
+ kind: z8.literal("split"),
1266
1613
  ...label,
1267
- branches: z7.array(
1268
- z7.object({
1614
+ branches: z8.array(
1615
+ z8.object({
1269
1616
  key: SplitBranchKey,
1270
1617
  /** A whole percentage. Zero is allowed: a branch kept in the flow but switched off. */
1271
- weight: z7.number().int().min(0).max(100),
1618
+ weight: z8.number().int().min(0).max(100),
1272
1619
  next: StepId.nullable()
1273
1620
  }).strict()
1274
1621
  ).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")
1275
1622
  }).strict();
1276
- var GotoStep = z7.object({ kind: z7.literal("goto"), ...label, target: StepId.nullable() }).strict();
1623
+ var GotoStep = z8.object({ kind: z8.literal("goto"), ...label, target: StepId.nullable() }).strict();
1277
1624
  var MAX_STEP_VISITS = 20;
1278
- var WebhookStep = z7.object({ kind: z7.literal("webhook"), ...label, ...next, endpoint_id: Id.nullable(), payload: JsonValue.nullish() }).strict();
1279
- var NotifyRecipients = z7.union([z7.literal("owner"), z7.literal("admins"), z7.object({ member_id: Id }).strict()]);
1280
- var NotifyStep = z7.object({
1281
- kind: z7.literal("notify"),
1625
+ var WebhookStep = z8.object({ kind: z8.literal("webhook"), ...label, ...next, endpoint_id: Id.nullable(), payload: JsonValue.nullish() }).strict();
1626
+ var NotifyRecipients = z8.union([z8.literal("owner"), z8.literal("admins"), z8.object({ member_id: Id }).strict()]);
1627
+ var NotifyStep = z8.object({
1628
+ kind: z8.literal("notify"),
1282
1629
  ...label,
1283
1630
  ...next,
1284
1631
  to: NotifyRecipients,
1285
1632
  /** Empty while the step is being written; `not_chosen` refuses to publish it. */
1286
- subject: z7.string().max(998),
1287
- body: z7.string().max(1e4)
1633
+ subject: z8.string().max(998),
1634
+ body: z8.string().max(1e4)
1288
1635
  }).strict();
1289
- var AutomationStep = z7.discriminatedUnion("kind", [
1636
+ var AutomationStep = z8.discriminatedUnion("kind", [
1290
1637
  EmailStep,
1291
1638
  DelayStep,
1292
1639
  ConditionStep,
1293
- z7.object({ kind: z7.literal("add_tag"), ...label, ...next, tag: DraftSlug }).strict(),
1294
- z7.object({ kind: z7.literal("remove_tag"), ...label, ...next, tag: DraftSlug }).strict(),
1640
+ z8.object({ kind: z8.literal("add_tag"), ...label, ...next, tag: DraftSlug }).strict(),
1641
+ z8.object({ kind: z8.literal("remove_tag"), ...label, ...next, tag: DraftSlug }).strict(),
1295
1642
  /** `value` null clears the property, as a contact upsert does. */
1296
- z7.object({ kind: z7.literal("set_property"), ...label, ...next, key: DraftPropertyKey, value: JsonValue }).strict(),
1643
+ z8.object({ kind: z8.literal("set_property"), ...label, ...next, key: DraftPropertyKey, value: JsonValue }).strict(),
1297
1644
  /** Subscribing never lifts a suppression: the service checks that, not the flow. */
1298
- z7.object({ kind: z7.literal("subscribe_topic"), ...label, ...next, topic: DraftSlug }).strict(),
1645
+ z8.object({ kind: z8.literal("subscribe_topic"), ...label, ...next, topic: DraftSlug }).strict(),
1299
1646
  /** `topic` unsubscribes from the automation's own topic, `all` from every topic. */
1300
- z7.object({ kind: z7.literal("unsubscribe"), ...label, ...next, scope: z7.enum(["topic", "all"]) }).strict(),
1301
- z7.object({ kind: z7.literal("exit"), ...label }).strict(),
1647
+ z8.object({ kind: z8.literal("unsubscribe"), ...label, ...next, scope: z8.enum(["topic", "all"]) }).strict(),
1648
+ z8.object({ kind: z8.literal("exit"), ...label }).strict(),
1302
1649
  SplitStep,
1303
1650
  GotoStep,
1304
1651
  WebhookStep,
@@ -1322,25 +1669,25 @@ function stepTargets(step) {
1322
1669
  }
1323
1670
  }
1324
1671
  var MAX_STEPS = 200;
1325
- var AutomationFlow = z7.object({
1326
- triggers: z7.array(AutomationTrigger).max(MAX_TRIGGERS),
1672
+ var AutomationFlow = z8.object({
1673
+ triggers: z8.array(AutomationTrigger).max(MAX_TRIGGERS),
1327
1674
  /** The first step. `null` is an empty flow. */
1328
1675
  entry: StepId.nullable(),
1329
- steps: z7.record(StepId, AutomationStep)
1676
+ steps: z8.record(StepId, AutomationStep)
1330
1677
  }).strict();
1331
1678
  var emptyFlow = () => ({ triggers: [], entry: null, steps: {} });
1332
- var QuietHours = z7.object({
1679
+ var QuietHours = z8.object({
1333
1680
  start: TimeOfDay,
1334
1681
  end: TimeOfDay,
1335
- weekdays: z7.array(Weekday).min(1).max(7).nullish()
1682
+ weekdays: z8.array(Weekday).min(1).max(7).nullish()
1336
1683
  }).strict();
1337
1684
  var RE_ENTRY_MODES = ["off", "after_exit"];
1338
- var ReEntryMode = z7.enum(RE_ENTRY_MODES);
1685
+ var ReEntryMode = z8.enum(RE_ENTRY_MODES);
1339
1686
  var MAX_RE_ENTRY_COOLDOWN_SECONDS = 365 * 24 * 60 * 60;
1340
- var AutomationSettings = z7.object({
1687
+ var AutomationSettings = z8.object({
1341
1688
  re_entry: ReEntryMode,
1342
1689
  /** Seconds a person must wait after leaving before entering again. Only with `after_exit`. */
1343
- re_entry_cooldown_s: z7.number().int().min(0).max(MAX_RE_ENTRY_COOLDOWN_SECONDS).nullish(),
1690
+ re_entry_cooldown_s: z8.number().int().min(0).max(MAX_RE_ENTRY_COOLDOWN_SECONDS).nullish(),
1344
1691
  quiet_hours: QuietHours.nullish(),
1345
1692
  /** Checked before every step; when it matches, the run ends. */
1346
1693
  exit_filter: BoundedAutomationFilter.nullish()
@@ -1398,18 +1745,18 @@ var AUTOMATION_ISSUE_CODES = [
1398
1745
  /** A one-off date that is already over everywhere on earth, so it would fire for nobody. */
1399
1746
  "date_in_the_past"
1400
1747
  ];
1401
- var AutomationIssueCode = z7.enum(AUTOMATION_ISSUE_CODES);
1402
- var AutomationIssue = z7.object({
1748
+ var AutomationIssueCode = z8.enum(AUTOMATION_ISSUE_CODES);
1749
+ var AutomationIssue = z8.object({
1403
1750
  code: AutomationIssueCode,
1404
1751
  /** The step the issue is about, when it is about one. */
1405
1752
  step_id: StepId.nullable(),
1406
1753
  /** Where inside the document, for the canvas to point at a field. */
1407
- path: z7.array(z7.union([z7.string(), z7.number()])),
1408
- message: z7.string()
1754
+ path: z8.array(z8.union([z8.string(), z8.number()])),
1755
+ message: z8.string()
1409
1756
  });
1410
- var AutomationValidation = z7.object({
1411
- ok: z7.boolean(),
1412
- issues: z7.array(AutomationIssue)
1757
+ var AutomationValidation = z8.object({
1758
+ ok: z8.boolean(),
1759
+ issues: z8.array(AutomationIssue)
1413
1760
  });
1414
1761
  function validateAutomation(flow, settings, ctx) {
1415
1762
  const issues = [];
@@ -1800,7 +2147,7 @@ function cycles(flow) {
1800
2147
  return out.sort();
1801
2148
  }
1802
2149
  var AUTOMATION_STATUSES = ["draft", "active", "paused", "archived"];
1803
- var AutomationStatus = z7.enum(AUTOMATION_STATUSES);
2150
+ var AutomationStatus = z8.enum(AUTOMATION_STATUSES);
1804
2151
  var AUTOMATION_END_REASONS = [
1805
2152
  "completed",
1806
2153
  "exit_filter",
@@ -1819,9 +2166,9 @@ var AUTOMATION_END_REASONS = [
1819
2166
  "ended_by_hand",
1820
2167
  "failed"
1821
2168
  ];
1822
- var AutomationEndReason = z7.enum(AUTOMATION_END_REASONS);
2169
+ var AutomationEndReason = z8.enum(AUTOMATION_END_REASONS);
1823
2170
  var AUTOMATION_WAIT_REASONS = ["delay", "quiet_hours", "plan_limit", "sending", "condition"];
1824
- var AutomationWaitReason = z7.enum(AUTOMATION_WAIT_REASONS);
2171
+ var AutomationWaitReason = z8.enum(AUTOMATION_WAIT_REASONS);
1825
2172
  var AUTOMATION_ENTRY_REFUSALS = [
1826
2173
  "already_inside",
1827
2174
  "re_entry_off",
@@ -1832,26 +2179,26 @@ var AUTOMATION_ENTRY_REFUSALS = [
1832
2179
  "not_active",
1833
2180
  "unknown_contact"
1834
2181
  ];
1835
- var AutomationEntryRefusal = z7.enum(AUTOMATION_ENTRY_REFUSALS);
1836
- var AutomationCounts = z7.object({
1837
- started: z7.number().int().min(0),
1838
- in_progress: z7.number().int().min(0),
1839
- completed: z7.number().int().min(0),
1840
- exited: z7.number().int().min(0),
1841
- failed: z7.number().int().min(0)
1842
- });
1843
- var Automation = z7.object({
2182
+ var AutomationEntryRefusal = z8.enum(AUTOMATION_ENTRY_REFUSALS);
2183
+ var AutomationCounts = z8.object({
2184
+ started: z8.number().int().min(0),
2185
+ in_progress: z8.number().int().min(0),
2186
+ completed: z8.number().int().min(0),
2187
+ exited: z8.number().int().min(0),
2188
+ failed: z8.number().int().min(0)
2189
+ });
2190
+ var Automation = z8.object({
1844
2191
  id: Id,
1845
- name: z7.string().min(1).max(200),
2192
+ name: z8.string().min(1).max(200),
1846
2193
  status: AutomationStatus,
1847
2194
  /** Every automation sends under one topic; there are no transactional automations. */
1848
2195
  topic: Slug,
1849
2196
  provider_id: Id.nullable(),
1850
2197
  settings: AutomationSettings,
1851
2198
  /** The flow people are running through, null while the automation has never been published. */
1852
- published_version: z7.number().int().min(1).nullable(),
2199
+ published_version: z8.number().int().min(1).nullable(),
1853
2200
  /** The flow being edited. Increments on every save, like a template's version. */
1854
- draft_version: z7.number().int().min(1),
2201
+ draft_version: z8.number().int().min(1),
1855
2202
  counts: AutomationCounts,
1856
2203
  created_at: Timestamp,
1857
2204
  updated_at: Timestamp,
@@ -1864,16 +2211,16 @@ var AutomationWithFlow = Automation.extend({
1864
2211
  published_flow: AutomationFlow.nullable()
1865
2212
  });
1866
2213
  var AutomationSummary = Automation;
1867
- var AutomationCreate = z7.object({
1868
- name: z7.string().min(1).max(200),
2214
+ var AutomationCreate = z8.object({
2215
+ name: z8.string().min(1).max(200),
1869
2216
  topic: Slug,
1870
2217
  provider_id: Id.optional(),
1871
2218
  flow: AutomationFlow.optional(),
1872
2219
  settings: AutomationSettings.partial().optional()
1873
2220
  });
1874
- var AutomationUpdate = z7.object({
1875
- base_version: z7.number().int().min(1),
1876
- name: z7.string().min(1).max(200).optional(),
2221
+ var AutomationUpdate = z8.object({
2222
+ base_version: z8.number().int().min(1),
2223
+ name: z8.string().min(1).max(200).optional(),
1877
2224
  topic: Slug.optional(),
1878
2225
  provider_id: Id.optional(),
1879
2226
  flow: AutomationFlow.optional(),
@@ -1886,42 +2233,42 @@ var AutomationListQuery = PageQuery.extend({
1886
2233
  status: AutomationStatus.optional(),
1887
2234
  topic: Slug.optional()
1888
2235
  });
1889
- var AutomationPublishRequest = z7.object({
2236
+ var AutomationPublishRequest = z8.object({
1890
2237
  /**
1891
2238
  * Answer what this publish would do and change nothing, whatever the flow
1892
2239
  * looks like. Without it a publish that has no issues and moves nobody
1893
2240
  * simply goes ahead, which is right for an API caller and wrong for a
1894
2241
  * dialog that is only showing somebody what they are about to do.
1895
2242
  */
1896
- dry_run: z7.boolean().optional(),
1897
- confirm: z7.boolean().optional(),
2243
+ dry_run: z8.boolean().optional(),
2244
+ confirm: z8.boolean().optional(),
1898
2245
  /** Per removed step: the step its waiting runs move to, or null to end them. */
1899
- moves: z7.record(StepId, StepId.nullable()).optional(),
2246
+ moves: z8.record(StepId, StepId.nullable()).optional(),
1900
2247
  /** Also enter everyone who matches a trigger's condition right now. */
1901
- backfill: z7.boolean().optional()
2248
+ backfill: z8.boolean().optional()
1902
2249
  }).strict();
1903
- var AutomationPublishMove = z7.object({
2250
+ var AutomationPublishMove = z8.object({
1904
2251
  step_id: StepId,
1905
2252
  /** Why the step's people have to move: it is gone, or its delay changed. */
1906
- change: z7.enum(["removed", "delay_changed", "kind_changed"]),
1907
- waiting: z7.number().int().min(0),
2253
+ change: z8.enum(["removed", "delay_changed", "kind_changed"]),
2254
+ waiting: z8.number().int().min(0),
1908
2255
  /** Where they go: a step id, or null to end their runs with `step_removed`. */
1909
2256
  destination: StepId.nullable()
1910
2257
  });
1911
- var AutomationPublishResult = z7.object({
1912
- published: z7.boolean(),
2258
+ var AutomationPublishResult = z8.object({
2259
+ published: z8.boolean(),
1913
2260
  automation: Automation,
1914
2261
  /** Empty when nothing has to move; non-empty and `published` false asks for a confirmation. */
1915
- moves: z7.array(AutomationPublishMove),
2262
+ moves: z8.array(AutomationPublishMove),
1916
2263
  /** How many people the backfill entered, or would enter when it is not confirmed. */
1917
- backfill: z7.number().int().min(0),
1918
- issues: z7.array(AutomationIssue)
1919
- });
1920
- var AutomationDuplicateRequest = z7.object({ name: z7.string().min(1).max(200).optional() }).strict();
1921
- var AutomationEnrollRequest = z7.object({
1922
- contact_ids: z7.array(Id).min(1).max(1e3).optional(),
1923
- external_ids: z7.array(z7.string().min(1).max(255)).min(1).max(1e3).optional(),
1924
- emails: z7.array(Email).min(1).max(1e3).optional(),
2264
+ backfill: z8.number().int().min(0),
2265
+ issues: z8.array(AutomationIssue)
2266
+ });
2267
+ var AutomationDuplicateRequest = z8.object({ name: z8.string().min(1).max(200).optional() }).strict();
2268
+ var AutomationEnrollRequest = z8.object({
2269
+ contact_ids: z8.array(Id).min(1).max(1e3).optional(),
2270
+ external_ids: z8.array(z8.string().min(1).max(255)).min(1).max(1e3).optional(),
2271
+ emails: z8.array(Email).min(1).max(1e3).optional(),
1925
2272
  segment_id: Id.optional(),
1926
2273
  /** Where to start them; the flow's entry step by default. */
1927
2274
  step_id: StepId.optional()
@@ -1929,34 +2276,34 @@ var AutomationEnrollRequest = z7.object({
1929
2276
  (v) => [v.contact_ids, v.external_ids, v.emails, v.segment_id].filter((x) => x !== void 0).length === 1,
1930
2277
  "name the people exactly one way: contact_ids, external_ids, emails or segment_id"
1931
2278
  );
1932
- var AutomationEnrollment = z7.object({
2279
+ var AutomationEnrollment = z8.object({
1933
2280
  contact_id: Id.nullable(),
1934
2281
  email: Email.nullable(),
1935
- external_id: z7.string().nullable(),
1936
- entered: z7.boolean(),
2282
+ external_id: z8.string().nullable(),
2283
+ entered: z8.boolean(),
1937
2284
  run_id: Id.nullable(),
1938
2285
  reason: AutomationEntryRefusal.nullable()
1939
2286
  });
1940
- var AutomationEnrollResult = z7.object({
1941
- entered: z7.number().int().min(0),
1942
- refused: z7.number().int().min(0),
1943
- results: z7.array(AutomationEnrollment)
2287
+ var AutomationEnrollResult = z8.object({
2288
+ entered: z8.number().int().min(0),
2289
+ refused: z8.number().int().min(0),
2290
+ results: z8.array(AutomationEnrollment)
1944
2291
  });
1945
- var AutomationRunStatus = z7.enum(["active", "waiting", "completed", "exited", "failed"]);
1946
- var AutomationRun = z7.object({
2292
+ var AutomationRunStatus = z8.enum(["active", "waiting", "completed", "exited", "failed"]);
2293
+ var AutomationRun = z8.object({
1947
2294
  id: Id,
1948
2295
  automation_id: Id,
1949
2296
  contact_id: Id,
1950
2297
  email: Email,
1951
- external_id: z7.string().nullable(),
1952
- version: z7.number().int().min(1),
2298
+ external_id: z8.string().nullable(),
2299
+ version: z8.number().int().min(1),
1953
2300
  status: AutomationRunStatus,
1954
2301
  current_step_id: StepId.nullable(),
1955
2302
  next_action_at: Timestamp.nullable(),
1956
2303
  wait_reason: AutomationWaitReason.nullable(),
1957
- end_reason: z7.string().nullable(),
2304
+ end_reason: z8.string().nullable(),
1958
2305
  /** What went wrong the last time this run tried to take its step, null otherwise. */
1959
- last_error: z7.string().nullable(),
2306
+ last_error: z8.string().nullable(),
1960
2307
  entered_at: Timestamp,
1961
2308
  ended_at: Timestamp.nullable()
1962
2309
  });
@@ -1966,13 +2313,13 @@ var AutomationRunListQuery = PageQuery.extend({
1966
2313
  contact_id: Id.optional()
1967
2314
  });
1968
2315
  var ContactAutomationRun = AutomationRun.extend({
1969
- automation: z7.object({ id: Id, name: z7.string(), status: AutomationStatus })
2316
+ automation: z8.object({ id: Id, name: z8.string(), status: AutomationStatus })
1970
2317
  });
1971
2318
  var ContactAutomationListQuery = PageQuery.extend({ status: AutomationRunStatus.optional() });
1972
- var AutomationStepExecution = z7.object({
2319
+ var AutomationStepExecution = z8.object({
1973
2320
  step_id: StepId,
1974
2321
  step_kind: AutomationStepKind,
1975
- visit: z7.number().int().min(1),
2322
+ visit: z8.number().int().min(1),
1976
2323
  started_at: Timestamp,
1977
2324
  finished_at: Timestamp.nullable(),
1978
2325
  /**
@@ -1984,48 +2331,48 @@ var AutomationStepExecution = z7.object({
1984
2331
  */
1985
2332
  outcome: Properties
1986
2333
  });
1987
- var AutomationJourney = z7.object({
2334
+ var AutomationJourney = z8.object({
1988
2335
  run: AutomationRun,
1989
- steps: z7.array(AutomationStepExecution)
2336
+ steps: z8.array(AutomationStepExecution)
1990
2337
  });
1991
- var AutomationStepReport = z7.object({
2338
+ var AutomationStepReport = z8.object({
1992
2339
  step_id: StepId,
1993
2340
  step_kind: AutomationStepKind,
1994
- reached: z7.number().int().min(0),
1995
- waiting: z7.number().int().min(0),
1996
- passed: z7.number().int().min(0),
2341
+ reached: z8.number().int().min(0),
2342
+ waiting: z8.number().int().min(0),
2343
+ passed: z8.number().int().min(0),
1997
2344
  /** Per branch, for a condition: how many took `yes` and how many `no`. */
1998
- branches: z7.record(z7.number().int().min(0)),
2345
+ branches: z8.record(z8.number().int().min(0)),
1999
2346
  /** The step mailing's id, for an email step of the published version. */
2000
2347
  mailing_id: Id.nullable()
2001
2348
  });
2002
- var AutomationReport = z7.object({
2349
+ var AutomationReport = z8.object({
2003
2350
  counts: AutomationCounts,
2004
2351
  /** Why the people who left did: `AutomationEndReason` to a count. */
2005
- end_reasons: z7.record(z7.number().int().min(0)),
2352
+ end_reasons: z8.record(z8.number().int().min(0)),
2006
2353
  /**
2007
2354
  * Why people a trigger named did not enter: `AutomationEntryRefusal` to a
2008
2355
  * count. Without it a pause, an exclusion or a missing subscription would
2009
2356
  * turn people away invisibly.
2010
2357
  */
2011
- refusals: z7.record(z7.number().int().min(0)),
2012
- steps: z7.array(AutomationStepReport)
2013
- });
2014
- var AutomationExitRunRequest = z7.object({ reason: z7.string().max(200).optional() }).strict();
2015
- var AutomationRunParams = z7.object({ id: Id, run_id: Id });
2016
- var AutomationStepParams = z7.object({ id: Id, step_id: StepId });
2017
- var AutomationTestEmailRequest = z7.object({ to: Email, merge: Properties.optional(), locale: LocaleTag.optional() }).strict();
2018
- var AutomationValidateRequest = z7.object({ flow: AutomationFlow.optional(), settings: AutomationSettings.partial().optional() }).strict();
2019
- var EventCreate = z7.object({
2358
+ refusals: z8.record(z8.number().int().min(0)),
2359
+ steps: z8.array(AutomationStepReport)
2360
+ });
2361
+ var AutomationExitRunRequest = z8.object({ reason: z8.string().max(200).optional() }).strict();
2362
+ var AutomationRunParams = z8.object({ id: Id, run_id: Id });
2363
+ var AutomationStepParams = z8.object({ id: Id, step_id: StepId });
2364
+ var AutomationTestEmailRequest = z8.object({ to: Email, merge: Properties.optional(), locale: LocaleTag.optional() }).strict();
2365
+ var AutomationValidateRequest = z8.object({ flow: AutomationFlow.optional(), settings: AutomationSettings.partial().optional() }).strict();
2366
+ var EventCreate = z8.object({
2020
2367
  name: EventName,
2021
- contact: z7.object({ external_id: z7.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
+ contact: z8.object({ external_id: z8.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"),
2022
2369
  data: Properties.optional(),
2023
2370
  occurred_at: Timestamp.optional(),
2024
- id: z7.string().min(1).max(128).optional()
2371
+ id: z8.string().min(1).max(128).optional()
2025
2372
  }).strict();
2026
- var Event = z7.object({
2373
+ var Event = z8.object({
2027
2374
  id: Id,
2028
- client_event_id: z7.string().nullable(),
2375
+ client_event_id: z8.string().nullable(),
2029
2376
  name: EventName,
2030
2377
  contact_id: Id,
2031
2378
  data: Properties,
@@ -2036,53 +2383,27 @@ var Event = z7.object({
2036
2383
  * hand. Each is then admitted or refused by the runtime on its own rules
2037
2384
  * (consent, exclusions, re-entry), which `automations.report` counts.
2038
2385
  */
2039
- entered: z7.number().int().min(0)
2386
+ entered: z8.number().int().min(0)
2040
2387
  });
2041
2388
  var EventListQuery = PageQuery.extend({
2042
2389
  name: EventName.optional(),
2043
2390
  contact_id: Id.optional()
2044
2391
  });
2045
2392
 
2046
- // src/unsubscribe.ts
2047
- var UNSUBSCRIBE_PATH_PREFIX = "/u/";
2048
- var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
2049
- var RESERVED_MERGE_FIELDS = {
2050
- /** From the recipient's merge values, else the contact. Supports a fallback. */
2051
- first_name: { source: "contact", fallback: true, required_for_broadcast: false },
2052
- last_name: { source: "contact", fallback: true, required_for_broadcast: false },
2053
- email: { source: "contact", fallback: false, required_for_broadcast: false },
2054
- /** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
2055
- unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
2056
- };
2057
- var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
2058
- function findMergeFields(html) {
2059
- const uses = [];
2060
- for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
2061
- uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
2062
- }
2063
- return uses;
2064
- }
2065
- function missingRequiredMergeFields(html) {
2066
- const used = new Set(findMergeFields(html).map((u) => u.name));
2067
- return Object.keys(RESERVED_MERGE_FIELDS).filter(
2068
- (name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
2069
- );
2070
- }
2071
-
2072
2393
  // src/workspace.ts
2073
- var CompanyId = z8.string().min(1).max(64);
2394
+ var CompanyId = z9.string().min(1).max(64);
2074
2395
  var ASSET_POLICIES = ["any", "service_only"];
2075
- var AssetPolicy = z8.enum(ASSET_POLICIES);
2396
+ var AssetPolicy = z9.enum(ASSET_POLICIES);
2076
2397
  var MAX_ALLOWED_ASSET_HOSTS = 20;
2077
- var AssetHost = z8.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");
2398
+ var AssetHost = z9.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");
2078
2399
  var MAX_MERGE_DEFAULTS = 50;
2079
2400
  var MAX_MERGE_DEFAULT_LENGTH = 2e3;
2080
- var MergeFieldName = z8.string().regex(/^[a-z][a-z0-9_]*$/, "a merge-field name: lowercase letters, digits and underscores, starting with a letter").max(64);
2081
- var MergeDefaultValue = z8.union([z8.string().max(MAX_MERGE_DEFAULT_LENGTH), z8.number().finite(), z8.boolean()]);
2082
- var MergeDefaults = z8.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), {
2401
+ var MergeFieldName = z9.string().regex(/^[a-z][a-z0-9_]*$/, "a merge-field name: lowercase letters, digits and underscores, starting with a letter").max(64);
2402
+ var MergeDefaultValue = z9.union([z9.string().max(MAX_MERGE_DEFAULT_LENGTH), z9.number().finite(), z9.boolean()]);
2403
+ var MergeDefaults = z9.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), {
2083
2404
  message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
2084
2405
  });
2085
- var WorkspaceSettings = z8.object({
2406
+ var WorkspaceSettings = z9.object({
2086
2407
  /**
2087
2408
  * The workspace's main language (a {@link LocaleTag}, e.g. "de"): the main
2088
2409
  * language of a new template, and of the hosted pages when nothing else
@@ -2095,9 +2416,9 @@ var WorkspaceSettings = z8.object({
2095
2416
  * each; the hosted pages are offered in those of them they support (English,
2096
2417
  * German, French, Italian, Spanish). The service stores canonical casing.
2097
2418
  */
2098
- locales: z8.array(LocaleTag).min(1).max(20).refine((l) => new Set(l.map((t) => t.toLowerCase())).size === l.length, "a language appears twice"),
2419
+ locales: z9.array(LocaleTag).min(1).max(20).refine((l) => new Set(l.map((t) => t.toLowerCase())).size === l.length, "a language appears twice"),
2099
2420
  /** Open and click tracking. Off by default, opt-in per workspace. */
2100
- tracking_enabled: z8.boolean(),
2421
+ tracking_enabled: z9.boolean(),
2101
2422
  /** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
2102
2423
  asset_policy: AssetPolicy,
2103
2424
  /**
@@ -2105,7 +2426,7 @@ var WorkspaceSettings = z8.object({
2105
2426
  * the workspace's website, typically. Empty by default. Ignored under `any`,
2106
2427
  * which allows everything already.
2107
2428
  */
2108
- allowed_asset_hosts: z8.array(AssetHost).max(MAX_ALLOWED_ASSET_HOSTS),
2429
+ allowed_asset_hosts: z9.array(AssetHost).max(MAX_ALLOWED_ASSET_HOSTS),
2109
2430
  /** Merge values every mail of the workspace can use (see `MergeDefaults`). Empty by default. */
2110
2431
  merge_defaults: MergeDefaults,
2111
2432
  /**
@@ -2114,10 +2435,10 @@ var WorkspaceSettings = z8.object({
2114
2435
  */
2115
2436
  default_timezone: TimezoneName.nullable()
2116
2437
  });
2117
- var Workspace = z8.object({
2438
+ var Workspace = z9.object({
2118
2439
  id: Id,
2119
2440
  slug: Slug,
2120
- name: z8.string().min(1).max(120),
2441
+ name: z9.string().min(1).max(120),
2121
2442
  settings: WorkspaceSettings,
2122
2443
  /**
2123
2444
  * The auth-brain company (tenant) the workspace was created for, or null for
@@ -2128,13 +2449,13 @@ var Workspace = z8.object({
2128
2449
  created_at: Timestamp,
2129
2450
  updated_at: Timestamp
2130
2451
  });
2131
- var WorkspaceCreate = z8.object({
2452
+ var WorkspaceCreate = z9.object({
2132
2453
  slug: Slug,
2133
- name: z8.string().min(1).max(120),
2454
+ name: z9.string().min(1).max(120),
2134
2455
  settings: WorkspaceSettings.partial().optional(),
2135
- owner: z8.object({
2456
+ owner: z9.object({
2136
2457
  email: Email,
2137
- name: z8.string().max(200).nullable().optional()
2458
+ name: z9.string().max(200).nullable().optional()
2138
2459
  }),
2139
2460
  /**
2140
2461
  * The signed-in person's active auth-brain company. The dashboard always
@@ -2144,49 +2465,49 @@ var WorkspaceCreate = z8.object({
2144
2465
  */
2145
2466
  company_id: CompanyId.optional()
2146
2467
  });
2147
- var WorkspaceUpdate = z8.object({
2148
- name: z8.string().min(1).max(120),
2468
+ var WorkspaceUpdate = z9.object({
2469
+ name: z9.string().min(1).max(120),
2149
2470
  settings: WorkspaceSettings.partial()
2150
2471
  }).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
2151
- var WorkspaceMove = z8.object({ company_id: CompanyId });
2472
+ var WorkspaceMove = z9.object({ company_id: CompanyId });
2152
2473
  var MEMBER_ROLES = ["owner", "admin", "editor", "viewer"];
2153
- var MemberRole = z8.enum(MEMBER_ROLES);
2474
+ var MemberRole = z9.enum(MEMBER_ROLES);
2154
2475
  var WorkspaceMembership = Workspace.extend({ role: MemberRole });
2155
- var Member = z8.object({
2476
+ var Member = z9.object({
2156
2477
  id: Id,
2157
2478
  /** The person's auth-brain subject. */
2158
- subject: z8.string().min(1).max(255),
2479
+ subject: z9.string().min(1).max(255),
2159
2480
  email: Email,
2160
- name: z8.string().max(200).nullable(),
2481
+ name: z9.string().max(200).nullable(),
2161
2482
  role: MemberRole,
2162
2483
  created_at: Timestamp
2163
2484
  });
2164
- var MemberCreate = z8.object({
2165
- subject: z8.string().min(1).max(255),
2485
+ var MemberCreate = z9.object({
2486
+ subject: z9.string().min(1).max(255),
2166
2487
  email: Email,
2167
- name: z8.string().max(200).nullable().optional(),
2488
+ name: z9.string().max(200).nullable().optional(),
2168
2489
  role: MemberRole
2169
2490
  });
2170
- var MemberUpdate = z8.object({ role: MemberRole });
2491
+ var MemberUpdate = z9.object({ role: MemberRole });
2171
2492
  var API_KEY_SCOPES = ["full", "read", "send"];
2172
- var ApiKeyScope = z8.enum(API_KEY_SCOPES);
2173
- var ApiKey = z8.object({
2493
+ var ApiKeyScope = z9.enum(API_KEY_SCOPES);
2494
+ var ApiKey = z9.object({
2174
2495
  id: Id,
2175
- name: z8.string().min(1).max(120),
2496
+ name: z9.string().min(1).max(120),
2176
2497
  /** The first characters of the key, for recognising it in a list. */
2177
- prefix: z8.string().min(4).max(32),
2498
+ prefix: z9.string().min(4).max(32),
2178
2499
  scope: ApiKeyScope,
2179
2500
  last_used_at: Timestamp.nullable(),
2180
2501
  revoked_at: Timestamp.nullable(),
2181
2502
  created_at: Timestamp
2182
2503
  });
2183
- var ApiKeyCreate = z8.object({
2184
- name: z8.string().min(1).max(120),
2504
+ var ApiKeyCreate = z9.object({
2505
+ name: z9.string().min(1).max(120),
2185
2506
  /** Omitted means `full`. */
2186
2507
  scope: ApiKeyScope.optional()
2187
2508
  });
2188
- var ApiKeyCreated = z8.object({
2189
- key: z8.string().min(20),
2509
+ var ApiKeyCreated = z9.object({
2510
+ key: z9.string().min(20),
2190
2511
  api_key: ApiKey
2191
2512
  });
2192
2513
  var AUDIT_ACTIONS = [
@@ -2274,29 +2595,29 @@ var AUDIT_ACTIONS = [
2274
2595
  "automation.run_ended",
2275
2596
  "event.recorded"
2276
2597
  ];
2277
- var AuditAction = z8.enum(AUDIT_ACTIONS);
2278
- var AuditActor = z8.discriminatedUnion("type", [
2279
- z8.object({ type: z8.literal("member"), member_id: Id, subject: z8.string().min(1) }),
2280
- z8.object({
2281
- type: z8.literal("api_key"),
2598
+ var AuditAction = z9.enum(AUDIT_ACTIONS);
2599
+ var AuditActor = z9.discriminatedUnion("type", [
2600
+ z9.object({ type: z9.literal("member"), member_id: Id, subject: z9.string().min(1) }),
2601
+ z9.object({
2602
+ type: z9.literal("api_key"),
2282
2603
  api_key_id: Id,
2283
2604
  /**
2284
2605
  * Who the client's application said it was acting for, from
2285
2606
  * `ON_BEHALF_OF_HEADER`: reported by that application, never verified.
2286
2607
  * Absent when the call carried no label.
2287
2608
  */
2288
- on_behalf_of: z8.string().min(1).max(ON_BEHALF_OF_MAX_LENGTH).optional()
2609
+ on_behalf_of: z9.string().min(1).max(ON_BEHALF_OF_MAX_LENGTH).optional()
2289
2610
  }),
2290
2611
  /** The service itself (the worker, a bounce, the hosted unsubscribe page). */
2291
- z8.object({ type: z8.literal("system"), reason: z8.string().min(1).max(200) })
2612
+ z9.object({ type: z9.literal("system"), reason: z9.string().min(1).max(200) })
2292
2613
  ]);
2293
- var AuditEntry = z8.object({
2614
+ var AuditEntry = z9.object({
2294
2615
  id: Id,
2295
2616
  action: AuditAction,
2296
2617
  actor: AuditActor,
2297
- target_type: z8.string().min(1).max(64),
2618
+ target_type: z9.string().min(1).max(64),
2298
2619
  target_id: Id.nullable(),
2299
- details: z8.record(z8.unknown()),
2620
+ details: z9.record(z9.unknown()),
2300
2621
  created_at: Timestamp
2301
2622
  });
2302
2623
  var AuditQuery = PageQuery.extend({
@@ -2305,12 +2626,12 @@ var AuditQuery = PageQuery.extend({
2305
2626
  });
2306
2627
 
2307
2628
  // src/invites.ts
2308
- import { z as z9 } from "zod";
2629
+ import { z as z10 } from "zod";
2309
2630
  var INVITE_STATUSES = ["pending", "accepted", "revoked", "expired"];
2310
- var InviteStatus = z9.enum(INVITE_STATUSES);
2631
+ var InviteStatus = z10.enum(INVITE_STATUSES);
2311
2632
  var DEFAULT_INVITE_TTL_DAYS = 7;
2312
2633
  var MAX_INVITE_TTL_DAYS = 30;
2313
- var Invite = z9.object({
2634
+ var Invite = z10.object({
2314
2635
  id: Id,
2315
2636
  /** Stored lowercased; only a person signed in with this address can accept. */
2316
2637
  email: Email,
@@ -2318,32 +2639,32 @@ var Invite = z9.object({
2318
2639
  /** Derived from the timestamps below, never stored. */
2319
2640
  status: InviteStatus,
2320
2641
  /** The member who invited, while they are still one (null after they left). */
2321
- invited_by: z9.object({ member_id: Id.nullable(), email: Email.nullable() }),
2642
+ invited_by: z10.object({ member_id: Id.nullable(), email: Email.nullable() }),
2322
2643
  expires_at: Timestamp,
2323
2644
  accepted_at: Timestamp.nullable(),
2324
2645
  revoked_at: Timestamp.nullable(),
2325
2646
  created_at: Timestamp
2326
2647
  });
2327
- var InviteCreate = z9.object({
2648
+ var InviteCreate = z10.object({
2328
2649
  email: Email,
2329
2650
  role: MemberRole,
2330
- expires_in_days: z9.number().int().min(1).max(MAX_INVITE_TTL_DAYS).optional()
2651
+ expires_in_days: z10.number().int().min(1).max(MAX_INVITE_TTL_DAYS).optional()
2331
2652
  });
2332
- var InviteCreated = z9.object({
2653
+ var InviteCreated = z10.object({
2333
2654
  invite: Invite,
2334
- token: z9.string().min(32).max(128)
2655
+ token: z10.string().min(32).max(128)
2335
2656
  });
2336
2657
  var InviteListQuery = PageQuery.extend({
2337
2658
  status: InviteStatus.optional()
2338
2659
  });
2339
- var InviteAccept = z9.object({
2340
- token: z9.string().min(32).max(128),
2660
+ var InviteAccept = z10.object({
2661
+ token: z10.string().min(32).max(128),
2341
2662
  email: Email,
2342
- name: z9.string().max(200).nullable().optional()
2663
+ name: z10.string().max(200).nullable().optional()
2343
2664
  });
2344
- var InviteAccepted = z9.object({
2665
+ var InviteAccepted = z10.object({
2345
2666
  workspace: WorkspaceMembership,
2346
- already_member: z9.boolean()
2667
+ already_member: z10.boolean()
2347
2668
  });
2348
2669
  var INVITE_REFUSAL_REASONS = ["email_mismatch", "inviter_lacks_role", "expired", "revoked", "accepted"];
2349
2670
 
@@ -2385,6 +2706,7 @@ function storedTemplateLine(value) {
2385
2706
  function buildTemplatePatch(args) {
2386
2707
  const { loaded, main, documentChanged, translations, changed, removed } = args;
2387
2708
  const out = {};
2709
+ if (args.insertions !== void 0 && !sameJson(args.insertions, loaded.insertions ?? {})) out.insertions = args.insertions;
2388
2710
  const subject = storedTemplateLine(main.subject);
2389
2711
  if (subject !== storedTemplateLine(loaded.subject)) out.subject = subject;
2390
2712
  const preheader = storedTemplateLine(main.preheader);
@@ -2394,7 +2716,13 @@ function buildTemplatePatch(args) {
2394
2716
  for (const tag of changed) {
2395
2717
  const version = translations[tag];
2396
2718
  if (version && !removed.has(tag)) {
2397
- patch[tag] = { subject: storedTemplateLine(version.subject), preheader: storedTemplateLine(version.preheader), document: version.document, status: version.status };
2719
+ patch[tag] = {
2720
+ subject: storedTemplateLine(version.subject),
2721
+ preheader: storedTemplateLine(version.preheader),
2722
+ document: version.document,
2723
+ status: version.status,
2724
+ ...version.insertions !== void 0 ? { insertions: version.insertions } : {}
2725
+ };
2398
2726
  }
2399
2727
  }
2400
2728
  for (const tag of removed) {
@@ -2403,6 +2731,10 @@ function buildTemplatePatch(args) {
2403
2731
  if (Object.keys(patch).length > 0) out.translations = patch;
2404
2732
  return out;
2405
2733
  }
2734
+ function sameJson(a, b) {
2735
+ 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;
2736
+ return JSON.stringify(norm(a)) === JSON.stringify(norm(b));
2737
+ }
2406
2738
  function restorePatch(old, current, workspaceLocales) {
2407
2739
  const oldMain = canonicalLocale(old.locale) ?? old.locale;
2408
2740
  const listed = workspaceLocales ? new Set(workspaceLocales.map((t) => canonicalLocale(t) ?? t)) : null;
@@ -2417,7 +2749,7 @@ function restorePatch(old, current, workspaceLocales) {
2417
2749
  untouched.add(canonical);
2418
2750
  continue;
2419
2751
  }
2420
- patch[canonical] = version;
2752
+ patch[canonical] = { ...version, insertions: version.insertions ?? {} };
2421
2753
  }
2422
2754
  for (const tag of Object.keys(current.translations)) {
2423
2755
  const canonical = canonicalLocale(tag) ?? tag;
@@ -2429,15 +2761,20 @@ function restorePatch(old, current, workspaceLocales) {
2429
2761
  subject: old.subject,
2430
2762
  preheader: old.preheader,
2431
2763
  document: old.document,
2764
+ insertions: old.insertions ?? {},
2432
2765
  ...Object.keys(patch).length > 0 ? { translations: patch } : {}
2433
2766
  };
2434
2767
  return { update, skipped: skipped.sort() };
2435
2768
  }
2436
2769
  function draftsFromTemplate(template) {
2770
+ const insertions = template.insertions ?? {};
2437
2771
  const translations = {};
2438
- for (const [tag, version] of Object.entries(template.translations)) translations[canonicalLocale(tag) ?? tag] = version;
2772
+ for (const [tag, version] of Object.entries(template.translations)) {
2773
+ translations[canonicalLocale(tag) ?? tag] = { ...version, insertions: alignTranslationInsertions(insertions, version.insertions) };
2774
+ }
2439
2775
  return {
2440
2776
  main: { subject: template.subject ?? "", preheader: template.preheader ?? "", document: template.document },
2777
+ insertions,
2441
2778
  translations,
2442
2779
  mainDocumentChanged: false,
2443
2780
  changed: [],
@@ -2461,13 +2798,19 @@ function addLanguageDraft(drafts, locale) {
2461
2798
  subject: drafts.main.subject,
2462
2799
  preheader: drafts.main.preheader,
2463
2800
  document: structuredClone(drafts.main.document),
2464
- status: "draft"
2801
+ status: "draft",
2802
+ insertions: copyInsertionTexts(drafts.insertions)
2465
2803
  }
2466
2804
  },
2467
2805
  changed: withChanged(drafts.changed, tag),
2468
2806
  removed: without(drafts.removed, tag)
2469
2807
  };
2470
2808
  }
2809
+ function copyInsertionTexts(insertions) {
2810
+ const out = {};
2811
+ for (const [name, def] of Object.entries(insertions)) out[name] = { choices: { ...def.choices }, filled: def.filled, otherwise: def.otherwise };
2812
+ return out;
2813
+ }
2471
2814
  function removeLanguageDraft(drafts, locale) {
2472
2815
  const tag = canonicalLocale(locale) ?? locale;
2473
2816
  const { [tag]: _gone, ...rest } = drafts.translations;
@@ -2500,6 +2843,7 @@ function setLanguageStatus(drafts, locale, status) {
2500
2843
  function draftsPatch(drafts, loaded) {
2501
2844
  return buildTemplatePatch({
2502
2845
  loaded,
2846
+ insertions: drafts.insertions,
2503
2847
  main: drafts.main,
2504
2848
  documentChanged: drafts.mainDocumentChanged,
2505
2849
  translations: drafts.translations,
@@ -2516,6 +2860,7 @@ function readyWithoutSubject(drafts) {
2516
2860
  function rebaseDrafts(sent, now, saved) {
2517
2861
  const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
2518
2862
  let out = draftsFromTemplate(saved);
2863
+ if (!sameJson(now.insertions, sent.insertions)) out = { ...out, insertions: now.insertions };
2519
2864
  if (now.main.subject !== sent.main.subject) out = editLanguageDraft(out, null, { subject: now.main.subject });
2520
2865
  if (now.main.preheader !== sent.main.preheader) out = editLanguageDraft(out, null, { preheader: now.main.preheader });
2521
2866
  out = same(now.main.document, sent.main.document) ? keepLanguageDocument(out, null, now.main.document) : editLanguageDraft(out, null, { document: now.main.document });
@@ -2538,17 +2883,321 @@ function rebaseDrafts(sent, now, saved) {
2538
2883
  removed: without(out.removed, tag)
2539
2884
  };
2540
2885
  }
2886
+ return sameJson(out.insertions, saved.insertions ?? {}) ? out : alignDrafts(out);
2887
+ }
2888
+ function alignDrafts(drafts) {
2889
+ const translations = {};
2890
+ for (const [tag, version] of Object.entries(drafts.translations)) {
2891
+ translations[tag] = { ...version, insertions: alignTranslationInsertions(drafts.insertions, version.insertions) };
2892
+ }
2893
+ return { ...drafts, translations };
2894
+ }
2895
+ function editMainInsertions(drafts, insertions) {
2896
+ return alignDrafts({ ...drafts, insertions });
2897
+ }
2898
+ function renameKey(record, from, to) {
2899
+ const out = {};
2900
+ for (const [k, v] of Object.entries(record)) out[k === from ? to : k] = v;
2541
2901
  return out;
2542
2902
  }
2903
+ function renameInsertion(drafts, from, to) {
2904
+ if (from === to || !ownEntry(drafts.insertions, from) || ownEntry(drafts.insertions, to)) return drafts;
2905
+ const translations = {};
2906
+ let changed = drafts.changed;
2907
+ for (const [tag, version] of Object.entries(drafts.translations)) {
2908
+ if (version.insertions && ownEntry(version.insertions, from)) {
2909
+ translations[tag] = { ...version, insertions: renameKey(version.insertions, from, to) };
2910
+ changed = withChanged(changed, tag);
2911
+ } else translations[tag] = version;
2912
+ }
2913
+ return { ...drafts, insertions: renameKey(drafts.insertions, from, to), translations, changed };
2914
+ }
2915
+ function renameInsertionChoice(drafts, name, from, to) {
2916
+ const def = ownEntry(drafts.insertions, name);
2917
+ if (!def || from === to || INSERTION_TEXT_SLOTS.includes(to) || ownEntry(def.choices, from) === void 0 || ownEntry(def.choices, to) !== void 0) {
2918
+ return drafts;
2919
+ }
2920
+ const translations = {};
2921
+ let changed = drafts.changed;
2922
+ for (const [tag, version] of Object.entries(drafts.translations)) {
2923
+ const t = ownEntry(version.insertions, name);
2924
+ if (t && Object.prototype.hasOwnProperty.call(t.choices, from)) {
2925
+ translations[tag] = { ...version, insertions: { ...version.insertions, [name]: { ...t, choices: renameKey(t.choices, from, to) } } };
2926
+ changed = withChanged(changed, tag);
2927
+ } else translations[tag] = version;
2928
+ }
2929
+ return {
2930
+ ...drafts,
2931
+ insertions: { ...drafts.insertions, [name]: { ...def, choices: renameKey(def.choices, from, to) } },
2932
+ translations,
2933
+ changed
2934
+ };
2935
+ }
2936
+ function editInsertionText(drafts, locale, name, choice, text) {
2937
+ const version = drafts.translations[locale];
2938
+ const def = ownEntry(drafts.insertions, name);
2939
+ if (!version || !def) return drafts;
2940
+ const aligned = alignTranslationInsertions(drafts.insertions, version.insertions);
2941
+ const current = aligned[name];
2942
+ let next2;
2943
+ if (choice === "filled") {
2944
+ if (def.filled === null) return drafts;
2945
+ next2 = { ...current, filled: text };
2946
+ } else if (choice === "otherwise") next2 = { ...current, otherwise: text };
2947
+ else if (Object.prototype.hasOwnProperty.call(def.choices, choice)) next2 = { ...current, choices: { ...current.choices, [choice]: text } };
2948
+ else return drafts;
2949
+ return {
2950
+ ...drafts,
2951
+ translations: { ...drafts.translations, [locale]: { ...version, insertions: { ...aligned, [name]: next2 } } },
2952
+ changed: withChanged(drafts.changed, locale)
2953
+ };
2954
+ }
2955
+ function missingInsertionTexts(drafts, locale) {
2956
+ const version = drafts.translations[locale];
2957
+ return version ? insertionCoverage(drafts.insertions, version.insertions) : [];
2958
+ }
2959
+ function readyWithoutInsertions(drafts) {
2960
+ 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);
2961
+ }
2962
+ function demotedBySave(drafts, loaded) {
2963
+ if (sameJson(drafts.insertions, loaded.insertions ?? {})) return [];
2964
+ 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();
2965
+ }
2966
+
2967
+ // src/insertion-editing.ts
2968
+ var has = (record, key) => Object.prototype.hasOwnProperty.call(record, key);
2969
+ function insertionNameProblem(insertions, name, current) {
2970
+ if (!InsertionName.safeParse(name).success) return "Use lowercase letters, digits and underscores, starting with a letter (like opening).";
2971
+ if (has(RESERVED_MERGE_FIELDS, name)) return `"${name}" is a merge field the service fills itself. Choose another name.`;
2972
+ if (name !== current && has(insertions, name)) return `There is already an insertion called "${name}".`;
2973
+ const chooser = Object.entries(insertions).find(([other, def]) => def.by === name && other !== current);
2974
+ if (chooser) return `"${name}" is the value that chooses "${chooser[0]}". Choose another name.`;
2975
+ if (current !== void 0 && ownEntry(insertions, current)?.by === name) return `"${name}" is the value that chooses this insertion. Choose another name.`;
2976
+ return null;
2977
+ }
2978
+ function insertionByProblem(insertions, by, self) {
2979
+ if (!InsertionName.safeParse(by).success) return "The value that chooses is a merge-field name: lowercase letters, digits and underscores (like reason).";
2980
+ if (by === "unsubscribe_url") return "The unsubscribe link cannot choose a text. Use a value the client or the contact provides.";
2981
+ if (by === self || has(insertions, by)) return `"${by}" is an insertion, not a value. Use a value the client or the contact provides.`;
2982
+ return null;
2983
+ }
2984
+ function normalizeChoiceKey(raw) {
2985
+ return raw.trim().toLowerCase();
2986
+ }
2987
+ function choiceKeyProblem(insertion, key, current) {
2988
+ if (INSERTION_TEXT_SLOTS.includes(key)) {
2989
+ return `"${key}" names the insertion's ${key === "filled" ? '"any other filled value"' : '"otherwise"'} text, so it cannot be a choice. Choose another value.`;
2990
+ }
2991
+ if (!InsertionChoiceKey.safeParse(key).success) return "A choice is the value a client sends: lowercase letters, digits, hyphens and underscores (like venue).";
2992
+ if (key !== current && has(insertion.choices, key)) return `There is already a choice "${key}".`;
2993
+ return null;
2994
+ }
2995
+ function addInsertion(drafts, rawName, rawBy) {
2996
+ const name = rawName.trim();
2997
+ const by = rawBy.trim();
2998
+ if (Object.keys(drafts.insertions).length >= MAX_INSERTIONS) return { ok: false, message: `A template holds at most ${MAX_INSERTIONS} insertions.` };
2999
+ const nameProblem = insertionNameProblem(drafts.insertions, name);
3000
+ if (nameProblem) return { ok: false, message: nameProblem };
3001
+ if (name === by) return { ok: false, message: "An insertion cannot be chosen by its own name. Use the value the client sends (like reason)." };
3002
+ const byProblem = insertionByProblem(drafts.insertions, by);
3003
+ if (byProblem) return { ok: false, message: byProblem };
3004
+ return { ok: true, drafts: editMainInsertions(drafts, { ...drafts.insertions, [name]: { by, choices: {}, filled: null, otherwise: "" } }) };
3005
+ }
3006
+ function removeInsertion(drafts, name) {
3007
+ if (!has(drafts.insertions, name)) return drafts;
3008
+ const { [name]: _gone, ...rest } = drafts.insertions;
3009
+ return editMainInsertions(drafts, rest);
3010
+ }
3011
+ function renameInsertionChecked(drafts, from, rawTo) {
3012
+ const to = rawTo.trim();
3013
+ if (to === from) return { ok: true, drafts };
3014
+ const problem = insertionNameProblem(drafts.insertions, to, from);
3015
+ if (problem) return { ok: false, message: problem };
3016
+ return { ok: true, drafts: renameInsertion(drafts, from, to) };
3017
+ }
3018
+ function setInsertionBy(drafts, name, rawBy) {
3019
+ const def = ownEntry(drafts.insertions, name);
3020
+ if (!def) return { ok: false, message: `There is no insertion "${name}".` };
3021
+ const by = rawBy.trim();
3022
+ const problem = insertionByProblem(drafts.insertions, by, name);
3023
+ if (problem) return { ok: false, message: problem };
3024
+ if (by === def.by) return { ok: true, drafts };
3025
+ return { ok: true, drafts: editMainInsertions(drafts, { ...drafts.insertions, [name]: { ...def, by } }) };
3026
+ }
3027
+ function addInsertionChoice(drafts, name, rawKey) {
3028
+ const def = ownEntry(drafts.insertions, name);
3029
+ if (!def) return { ok: false, message: `There is no insertion "${name}".` };
3030
+ if (Object.keys(def.choices).length >= MAX_INSERTION_CHOICES) return { ok: false, message: `An insertion holds at most ${MAX_INSERTION_CHOICES} choices.` };
3031
+ const key = normalizeChoiceKey(rawKey);
3032
+ const problem = choiceKeyProblem(def, key);
3033
+ if (problem) return { ok: false, message: problem };
3034
+ return { ok: true, drafts: editMainInsertions(drafts, { ...drafts.insertions, [name]: { ...def, choices: { ...def.choices, [key]: "" } } }) };
3035
+ }
3036
+ function removeInsertionChoice(drafts, name, key) {
3037
+ const def = ownEntry(drafts.insertions, name);
3038
+ if (!def || !has(def.choices, key)) return drafts;
3039
+ const { [key]: _gone, ...choices } = def.choices;
3040
+ return editMainInsertions(drafts, { ...drafts.insertions, [name]: { ...def, choices } });
3041
+ }
3042
+ function renameInsertionChoiceChecked(drafts, name, from, rawTo) {
3043
+ const def = ownEntry(drafts.insertions, name);
3044
+ if (!def) return { ok: false, message: `There is no insertion "${name}".` };
3045
+ const to = normalizeChoiceKey(rawTo);
3046
+ if (to === from) return { ok: true, drafts };
3047
+ const problem = choiceKeyProblem(def, to, from);
3048
+ if (problem) return { ok: false, message: problem };
3049
+ return { ok: true, drafts: renameInsertionChoice(drafts, name, from, to) };
3050
+ }
3051
+ function setInsertionFilled(drafts, name, on) {
3052
+ const def = ownEntry(drafts.insertions, name);
3053
+ if (!def || def.filled !== null === on) return drafts;
3054
+ return editMainInsertions(drafts, { ...drafts.insertions, [name]: { ...def, filled: on ? "" : null } });
3055
+ }
3056
+ function editMainInsertionText(drafts, name, choice, text) {
3057
+ const def = ownEntry(drafts.insertions, name);
3058
+ if (!def) return drafts;
3059
+ let next2;
3060
+ if (choice === "otherwise") next2 = { ...def, otherwise: text };
3061
+ else if (choice === "filled") {
3062
+ if (def.filled === null) return drafts;
3063
+ next2 = { ...def, filled: text };
3064
+ } else if (has(def.choices, choice)) next2 = { ...def, choices: { ...def.choices, [choice]: text } };
3065
+ else return drafts;
3066
+ return editMainInsertions(drafts, { ...drafts.insertions, [name]: next2 });
3067
+ }
3068
+ function mainInsertionText(insertion, choice) {
3069
+ if (choice === "otherwise") return insertion.otherwise;
3070
+ if (choice === "filled") return insertion.filled;
3071
+ return ownEntry(insertion.choices, choice) ?? null;
3072
+ }
3073
+ function translationTextFromInput(mainText, value) {
3074
+ if (value.trim() !== "") return value;
3075
+ return mainText !== null && mainText.trim() === "" ? "" : null;
3076
+ }
3077
+ function insertionTextSlots(insertion) {
3078
+ return [...Object.keys(insertion.choices), ...insertion.filled !== null ? ["filled"] : [], "otherwise"];
3079
+ }
3080
+ function insertionSlotLabel(choice) {
3081
+ if (choice === "filled") return "any other filled value";
3082
+ if (choice === "otherwise") return "otherwise";
3083
+ return `'${choice}'`;
3084
+ }
3085
+ function joinWords(items) {
3086
+ if (items.length <= 1) return items.join("");
3087
+ return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
3088
+ }
3089
+ function describeMissingTexts(missing) {
3090
+ if (missing.length === 0) return "";
3091
+ return `no text for ${joinWords(missing.map((m) => `${insertionSlotLabel(m.choice)} in ${m.insertion}`))}`;
3092
+ }
3093
+ function saveDemotions(drafts, loaded) {
3094
+ const tags = [.../* @__PURE__ */ new Set([...demotedBySave(drafts, loaded), ...readyWithoutInsertions(drafts)])].sort();
3095
+ return tags.map((locale) => ({ locale, missing: insertionCoverage(drafts.insertions, ownEntry(drafts.translations, locale)?.insertions) }));
3096
+ }
3097
+ function demoteForSave(drafts) {
3098
+ let out = drafts;
3099
+ for (const tag of readyWithoutInsertions(drafts)) out = setLanguageStatus(out, tag, "draft");
3100
+ return out;
3101
+ }
3102
+ function draftInsertionProblems(drafts) {
3103
+ const out = [];
3104
+ const parsed = Insertions.safeParse(drafts.insertions);
3105
+ if (!parsed.success) {
3106
+ for (const issue of parsed.error.issues) out.push({ locale: null, reason: "invalid", path: issue.path.join("."), message: issue.message });
3107
+ return out;
3108
+ }
3109
+ for (const p of insertionProblems(drafts.insertions)) out.push({ ...p, locale: null });
3110
+ const places = (subject, preheader, document) => ({ subject, preheader, document: JSON.stringify(document) });
3111
+ for (const p of placementProblems(places(drafts.main.subject, drafts.main.preheader, drafts.main.document), drafts.insertions)) out.push({ ...p, locale: null });
3112
+ for (const [tag, version] of Object.entries(drafts.translations)) {
3113
+ if (!drafts.changed.includes(tag)) continue;
3114
+ for (const p of translationInsertionProblems(drafts.insertions, version.insertions ?? {})) out.push({ ...p, locale: tag });
3115
+ for (const p of placementProblems(places(version.subject, version.preheader, version.document), drafts.insertions)) out.push({ ...p, locale: tag });
3116
+ }
3117
+ return out;
3118
+ }
3119
+ var PREVIEW_FILLED = ":filled";
3120
+ var PREVIEW_EMPTY = ":empty";
3121
+ function previewControls(insertions) {
3122
+ return Object.entries(insertionInputs(insertions)).map(([by, input]) => ({
3123
+ by,
3124
+ insertions: input.insertions,
3125
+ options: [
3126
+ ...input.keys.map((key) => ({ value: key, label: key })),
3127
+ ...input.filled ? [{ value: PREVIEW_FILLED, label: "any other value" }] : [],
3128
+ { value: PREVIEW_EMPTY, label: "empty" }
3129
+ ]
3130
+ }));
3131
+ }
3132
+ function defaultPreviewPick(control) {
3133
+ return control.options[0].value;
3134
+ }
3135
+ function effectivePreviewPicks(controls, picks) {
3136
+ const out = {};
3137
+ for (const control of controls) {
3138
+ const picked = ownEntry(picks, control.by);
3139
+ out[control.by] = picked !== void 0 && control.options.some((o) => o.value === picked) ? picked : defaultPreviewPick(control);
3140
+ }
3141
+ return out;
3142
+ }
3143
+ function sampleFilledValue(by, keys) {
3144
+ const sample = by === "first_name" ? "Alex" : by === "last_name" ? "Morgan" : "Sample value";
3145
+ return keys.includes(sample.toLowerCase()) ? "Sample value" : sample;
3146
+ }
3147
+ function previewInsertions(main, translation, missing) {
3148
+ if (translation === void 0) return main;
3149
+ const aligned = alignTranslationInsertions(main, translation);
3150
+ const out = {};
3151
+ for (const [name, def] of Object.entries(main)) {
3152
+ const t = ownEntry(aligned, name);
3153
+ const choices = {};
3154
+ for (const key of Object.keys(def.choices)) choices[key] = ownEntry(t.choices, key) ?? missing;
3155
+ out[name] = { by: def.by, choices, filled: def.filled === null ? null : t.filled ?? missing, otherwise: t.otherwise ?? missing };
3156
+ }
3157
+ return out;
3158
+ }
3159
+ function previewPersonalization(insertions, picks) {
3160
+ const values = {};
3161
+ const controlled = /* @__PURE__ */ new Set();
3162
+ const shown = effectivePreviewPicks(previewControls(insertions), picks);
3163
+ for (const [by, input] of Object.entries(insertionInputs(insertions))) {
3164
+ controlled.add(by);
3165
+ const pick = shown[by];
3166
+ if (pick === PREVIEW_EMPTY) continue;
3167
+ values[by] = pick === PREVIEW_FILLED ? sampleFilledValue(by, input.keys) : pick;
3168
+ }
3169
+ const sample = sampleMergeValue(values);
3170
+ return {
3171
+ insertions: resolveInsertions(insertions, (name) => ownEntry(values, name)),
3172
+ value: (name, fallback) => controlled.has(name) ? sample(name, fallback) : `{{${name}${fallback === void 0 ? "" : `|${fallback}`}}}`
3173
+ };
3174
+ }
3175
+ var REFUSAL_HINTS = {
3176
+ ready_needs_insertions: "Write the missing texts in that language, or set it back to draft, then save.",
3177
+ insertion_name: "Rename the insertion: the names first_name, last_name, email and unsubscribe_url are filled by the service.",
3178
+ insertion_by: "Choose the insertion by a value the client or the contact provides, not by the unsubscribe link or another insertion.",
3179
+ insertion_choice_key: "Use lowercase letters, digits, hyphens and underscores for a choice.",
3180
+ insertion_text: "An insertion text can hold merge fields such as {{first_name}}, but not another insertion.",
3181
+ insertion_with_fallback: 'Remove the |fallback from the insertion in the subject, preheader or body: its "otherwise" text is the fallback.',
3182
+ unknown_translation_insertion: "A language names an insertion or a choice the main language no longer has. Reload the template and try again."
3183
+ };
3184
+ function insertionRefusalHint(details) {
3185
+ const reason = details?.reason;
3186
+ if (typeof reason !== "string" || !has(REFUSAL_HINTS, reason)) return null;
3187
+ const hint = REFUSAL_HINTS[reason];
3188
+ if (reason !== "ready_needs_insertions") return hint;
3189
+ const missing = Array.isArray(details?.missing) ? details.missing.filter((m) => typeof m === "object" && m !== null && typeof m.insertion === "string" && typeof m.choice === "string") : [];
3190
+ return missing.length > 0 ? `The language has ${describeMissingTexts(missing)}. ${hint}` : hint;
3191
+ }
2543
3192
 
2544
3193
  // src/providers.ts
2545
- import { z as z10 } from "zod";
3194
+ import { z as z11 } from "zod";
2546
3195
  var PROVIDER_KINDS = ["smtp", "resend"];
2547
- var ProviderKind = z10.enum(PROVIDER_KINDS);
2548
- var ProviderPolicy = z10.object({
2549
- daily_recipient_budget: z10.number().int().min(1).max(1e7),
2550
- min_interval_ms: z10.number().int().min(0).max(36e5),
2551
- max_recipients_per_message: z10.number().int().min(1).max(1e3)
3196
+ var ProviderKind = z11.enum(PROVIDER_KINDS);
3197
+ var ProviderPolicy = z11.object({
3198
+ daily_recipient_budget: z11.number().int().min(1).max(1e7),
3199
+ min_interval_ms: z11.number().int().min(0).max(36e5),
3200
+ max_recipients_per_message: z11.number().int().min(1).max(1e3)
2552
3201
  });
2553
3202
  var ICLOUD_SMTP_POLICY = {
2554
3203
  daily_recipient_budget: 800,
@@ -2556,17 +3205,17 @@ var ICLOUD_SMTP_POLICY = {
2556
3205
  max_recipients_per_message: 1
2557
3206
  };
2558
3207
  var SMTP_SECURITY = ["tls", "starttls"];
2559
- var SmtpConfigBase = z10.object({
2560
- host: z10.string().min(1).max(255),
2561
- port: z10.number().int().min(1).max(65535),
3208
+ var SmtpConfigBase = z11.object({
3209
+ host: z11.string().min(1).max(255),
3210
+ port: z11.number().int().min(1).max(65535),
2562
3211
  /** `tls` connects encrypted (465), `starttls` upgrades (587). Never plaintext. */
2563
- security: z10.enum(SMTP_SECURITY),
2564
- username: z10.string().min(1).max(255)
3212
+ security: z11.enum(SMTP_SECURITY),
3213
+ username: z11.string().min(1).max(255)
2565
3214
  });
2566
- var ResendConfigBase = z10.object({});
2567
- var ProviderRejections = z10.object({
2568
- count: z10.number().int().min(0),
2569
- last_error: z10.string().nullable(),
3215
+ var ResendConfigBase = z11.object({});
3216
+ var ProviderRejections = z11.object({
3217
+ count: z11.number().int().min(0),
3218
+ last_error: z11.string().nullable(),
2570
3219
  last_at: Timestamp.nullable(),
2571
3220
  /**
2572
3221
  * The last time the bounce circuit breaker tripped on one of this provider's
@@ -2576,59 +3225,59 @@ var ProviderRejections = z10.object({
2576
3225
  * run's bounce blocks were undone and the mailing paused. `sample` is the
2577
3226
  * reply that tripped it.
2578
3227
  */
2579
- anomaly: z10.object({
3228
+ anomaly: z11.object({
2580
3229
  at: Timestamp,
2581
3230
  /** `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. */
2582
- scope: z10.enum(["mailing", "provider"]),
3231
+ scope: z11.enum(["mailing", "provider"]),
2583
3232
  /**
2584
3233
  * True while the provider-wide breaker is open: no bounce blocks through
2585
3234
  * this provider, test sends refused and mailings not started or resumed
2586
3235
  * (`provider_anomaly`), until an admin clears it (`providers.clearAnomaly`).
2587
3236
  */
2588
- blocking: z10.boolean(),
3237
+ blocking: z11.boolean(),
2589
3238
  mailing_id: Id.nullable(),
2590
- reason: z10.string(),
2591
- sample: z10.string()
3239
+ reason: z11.string(),
3240
+ sample: z11.string()
2592
3241
  }).nullable()
2593
3242
  });
2594
- var ProviderEvents = z10.object({
2595
- status: z10.enum(["active", "needs_secret"]),
2596
- source: z10.enum(["automatic", "manual"]).nullable(),
2597
- url: z10.string().url(),
2598
- error: z10.string().nullable(),
3243
+ var ProviderEvents = z11.object({
3244
+ status: z11.enum(["active", "needs_secret"]),
3245
+ source: z11.enum(["automatic", "manual"]).nullable(),
3246
+ url: z11.string().url(),
3247
+ error: z11.string().nullable(),
2599
3248
  /**
2600
3249
  * Events that named an email this provider never sent through the service
2601
3250
  * (another workspace or system sharing the Resend account): counted, never
2602
3251
  * acted on.
2603
3252
  */
2604
- unmatched: z10.number().int().min(0)
3253
+ unmatched: z11.number().int().min(0)
2605
3254
  });
2606
3255
  var RESEND_EVENT_TYPES = ["email.bounced", "email.complained", "email.delivery_delayed"];
2607
- var ResendSigningSecret = z10.string().max(200).regex(/^whsec_[A-Za-z0-9+/]{16,}={0,2}$/, "must be the signing secret Resend shows, starting with whsec_");
2608
- var ProviderEventsSecret = z10.object({ signing_secret: ResendSigningSecret });
3256
+ var ResendSigningSecret = z11.string().max(200).regex(/^whsec_[A-Za-z0-9+/]{16,}={0,2}$/, "must be the signing secret Resend shows, starting with whsec_");
3257
+ var ProviderEventsSecret = z11.object({ signing_secret: ResendSigningSecret });
2609
3258
  var ProviderFields = {
2610
- name: z10.string().min(1).max(120),
2611
- from_name: z10.string().min(1).max(120),
3259
+ name: z11.string().min(1).max(120),
3260
+ from_name: z11.string().min(1).max(120),
2612
3261
  from_email: Email,
2613
3262
  reply_to: Email.nullable(),
2614
3263
  policy: ProviderPolicy
2615
3264
  };
2616
- var Provider = z10.discriminatedUnion("kind", [
2617
- z10.object({
3265
+ var Provider = z11.discriminatedUnion("kind", [
3266
+ z11.object({
2618
3267
  id: Id,
2619
- kind: z10.literal("smtp"),
3268
+ kind: z11.literal("smtp"),
2620
3269
  config: SmtpConfigBase,
2621
- has_secret: z10.boolean(),
3270
+ has_secret: z11.boolean(),
2622
3271
  ...ProviderFields,
2623
3272
  rejections: ProviderRejections,
2624
3273
  created_at: Timestamp,
2625
3274
  updated_at: Timestamp
2626
3275
  }),
2627
- z10.object({
3276
+ z11.object({
2628
3277
  id: Id,
2629
- kind: z10.literal("resend"),
3278
+ kind: z11.literal("resend"),
2630
3279
  config: ResendConfigBase,
2631
- has_secret: z10.boolean(),
3280
+ has_secret: z11.boolean(),
2632
3281
  ...ProviderFields,
2633
3282
  rejections: ProviderRejections,
2634
3283
  events: ProviderEvents,
@@ -2636,31 +3285,31 @@ var Provider = z10.discriminatedUnion("kind", [
2636
3285
  updated_at: Timestamp
2637
3286
  })
2638
3287
  ]);
2639
- var ProviderCreate = z10.discriminatedUnion("kind", [
2640
- z10.object({
2641
- kind: z10.literal("smtp"),
2642
- config: SmtpConfigBase.extend({ password: z10.string().min(1).max(1024) }),
3288
+ var ProviderCreate = z11.discriminatedUnion("kind", [
3289
+ z11.object({
3290
+ kind: z11.literal("smtp"),
3291
+ config: SmtpConfigBase.extend({ password: z11.string().min(1).max(1024) }),
2643
3292
  ...ProviderFields
2644
3293
  }),
2645
- z10.object({
2646
- kind: z10.literal("resend"),
2647
- config: ResendConfigBase.extend({ api_key: z10.string().min(1).max(1024) }),
3294
+ z11.object({
3295
+ kind: z11.literal("resend"),
3296
+ config: ResendConfigBase.extend({ api_key: z11.string().min(1).max(1024) }),
2648
3297
  ...ProviderFields
2649
3298
  })
2650
3299
  ]);
2651
- var ProviderUpdate = z10.discriminatedUnion("kind", [
2652
- z10.object({
2653
- kind: z10.literal("smtp"),
2654
- config: SmtpConfigBase.extend({ password: z10.string().min(1).max(1024).optional() }).partial().optional(),
3300
+ var ProviderUpdate = z11.discriminatedUnion("kind", [
3301
+ z11.object({
3302
+ kind: z11.literal("smtp"),
3303
+ config: SmtpConfigBase.extend({ password: z11.string().min(1).max(1024).optional() }).partial().optional(),
2655
3304
  name: ProviderFields.name.optional(),
2656
3305
  from_name: ProviderFields.from_name.optional(),
2657
3306
  from_email: ProviderFields.from_email.optional(),
2658
3307
  reply_to: ProviderFields.reply_to.optional(),
2659
3308
  policy: ProviderPolicy.partial().optional()
2660
3309
  }),
2661
- z10.object({
2662
- kind: z10.literal("resend"),
2663
- config: ResendConfigBase.extend({ api_key: z10.string().min(1).max(1024).optional() }).optional(),
3310
+ z11.object({
3311
+ kind: z11.literal("resend"),
3312
+ config: ResendConfigBase.extend({ api_key: z11.string().min(1).max(1024).optional() }).optional(),
2664
3313
  name: ProviderFields.name.optional(),
2665
3314
  from_name: ProviderFields.from_name.optional(),
2666
3315
  from_email: ProviderFields.from_email.optional(),
@@ -2668,48 +3317,48 @@ var ProviderUpdate = z10.discriminatedUnion("kind", [
2668
3317
  policy: ProviderPolicy.partial().optional()
2669
3318
  })
2670
3319
  ]);
2671
- var ProviderUsage = z10.object({
3320
+ var ProviderUsage = z11.object({
2672
3321
  provider_id: Id,
2673
- recipients_last_24h: z10.number().int().min(0),
2674
- remaining_budget: z10.number().int().min(0),
3322
+ recipients_last_24h: z11.number().int().min(0),
3323
+ remaining_budget: z11.number().int().min(0),
2675
3324
  /** When enough of the rolling window frees up to send again, if exhausted. */
2676
3325
  next_capacity_at: Timestamp.nullable()
2677
3326
  });
2678
- var ProviderVerifyResult = z10.object({
2679
- ok: z10.boolean(),
2680
- error: z10.string().nullable()
3327
+ var ProviderVerifyResult = z11.object({
3328
+ ok: z11.boolean(),
3329
+ error: z11.string().nullable()
2681
3330
  });
2682
3331
 
2683
3332
  // src/contacts.ts
2684
- import { z as z11 } from "zod";
2685
- var Topic = z11.object({
3333
+ import { z as z12 } from "zod";
3334
+ var Topic = z12.object({
2686
3335
  id: Id,
2687
3336
  slug: Slug,
2688
- name: z11.string().min(1).max(120),
3337
+ name: z12.string().min(1).max(120),
2689
3338
  /** Shown on the hosted preference page. */
2690
- description: z11.string().max(1e3).nullable(),
3339
+ description: z12.string().max(1e3).nullable(),
2691
3340
  /** Translations of `name` and `description` per locale, for the hosted page. */
2692
- translations: z11.record(
2693
- z11.object({ name: z11.string().min(1).max(120), description: z11.string().max(1e3).nullable() })
3341
+ translations: z12.record(
3342
+ z12.object({ name: z12.string().min(1).max(120), description: z12.string().max(1e3).nullable() })
2694
3343
  ),
2695
3344
  created_at: Timestamp,
2696
3345
  updated_at: Timestamp
2697
3346
  });
2698
- var TopicCreate = z11.object({
3347
+ var TopicCreate = z12.object({
2699
3348
  slug: Slug,
2700
- name: z11.string().min(1).max(120),
2701
- description: z11.string().max(1e3).optional(),
3349
+ name: z12.string().min(1).max(120),
3350
+ description: z12.string().max(1e3).optional(),
2702
3351
  translations: Topic.shape.translations.optional()
2703
3352
  });
2704
3353
  var TopicUpdate = TopicCreate.omit({ slug: true }).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
2705
- var Contact = z11.object({
3354
+ var Contact = z12.object({
2706
3355
  id: Id,
2707
- external_id: z11.string().min(1).max(255).nullable(),
3356
+ external_id: z12.string().min(1).max(255).nullable(),
2708
3357
  /** Stored lowercased, unique per workspace. */
2709
3358
  email: Email,
2710
- first_name: z11.string().max(200).nullable(),
2711
- last_name: z11.string().max(200).nullable(),
2712
- locale: z11.string().min(2).max(35).nullable(),
3359
+ first_name: z12.string().max(200).nullable(),
3360
+ last_name: z12.string().max(200).nullable(),
3361
+ locale: z12.string().min(2).max(35).nullable(),
2713
3362
  /**
2714
3363
  * The person's own time zone, an IANA name such as `Europe/Berlin` (A1).
2715
3364
  * Every wall clock an automation waits for is read in it, falling back to the
@@ -2717,65 +3366,65 @@ var Contact = z11.object({
2717
3366
  */
2718
3367
  timezone: TimezoneName.nullable(),
2719
3368
  properties: Properties,
2720
- topics: z11.array(Slug),
3369
+ topics: z12.array(Slug),
2721
3370
  /** The slugs of the contact's tags (S4). */
2722
- tags: z11.array(Slug),
3371
+ tags: z12.array(Slug),
2723
3372
  created_at: Timestamp,
2724
3373
  updated_at: Timestamp
2725
3374
  });
2726
- var ContactUpsert = z11.object({
2727
- external_id: z11.string().min(1).max(255).optional(),
3375
+ var ContactUpsert = z12.object({
3376
+ external_id: z12.string().min(1).max(255).optional(),
2728
3377
  email: Email.optional(),
2729
- first_name: z11.string().max(200).nullable().optional(),
2730
- last_name: z11.string().max(200).nullable().optional(),
2731
- locale: z11.string().min(2).max(35).nullable().optional(),
3378
+ first_name: z12.string().max(200).nullable().optional(),
3379
+ last_name: z12.string().max(200).nullable().optional(),
3380
+ locale: z12.string().min(2).max(35).nullable().optional(),
2732
3381
  timezone: TimezoneName.nullable().optional(),
2733
3382
  properties: Properties.optional(),
2734
- topics: z11.array(Slug).max(100).optional()
3383
+ topics: z12.array(Slug).max(100).optional()
2735
3384
  }).refine((v) => v.external_id !== void 0 || v.email !== void 0, {
2736
3385
  message: "external_id or email is required",
2737
3386
  path: ["email"]
2738
3387
  });
2739
- var ContactUpsertResult = z11.object({
3388
+ var ContactUpsertResult = z12.object({
2740
3389
  contact: Contact,
2741
- created: z11.boolean()
3390
+ created: z12.boolean()
2742
3391
  });
2743
3392
  var ContactListQuery = PageQuery.extend({
2744
- email: z11.string().min(1).max(254).optional(),
2745
- external_id: z11.string().min(1).max(255).optional(),
3393
+ email: z12.string().min(1).max(254).optional(),
3394
+ external_id: z12.string().min(1).max(255).optional(),
2746
3395
  topic: Slug.optional()
2747
3396
  });
2748
- var ContactErased = z11.object({
2749
- ok: z11.literal(true),
2750
- erased_messages: z11.number().int().min(0),
2751
- erased_recipients: z11.number().int().min(0),
2752
- suppressions_kept: z11.number().int().min(0)
3397
+ var ContactErased = z12.object({
3398
+ ok: z12.literal(true),
3399
+ erased_messages: z12.number().int().min(0),
3400
+ erased_recipients: z12.number().int().min(0),
3401
+ suppressions_kept: z12.number().int().min(0)
2753
3402
  });
2754
3403
  var SUPPRESSION_REASONS = ["unsubscribed", "bounced", "complained", "manual"];
2755
- var SuppressionReason = z11.enum(SUPPRESSION_REASONS);
2756
- var Suppression = z11.object({
3404
+ var SuppressionReason = z12.enum(SUPPRESSION_REASONS);
3405
+ var Suppression = z12.object({
2757
3406
  id: Id,
2758
3407
  email: Email,
2759
3408
  reason: SuppressionReason,
2760
3409
  topic: Slug.nullable(),
2761
3410
  source_message_id: Id.nullable(),
2762
- note: z11.string().max(1e3).nullable(),
3411
+ note: z12.string().max(1e3).nullable(),
2763
3412
  created_at: Timestamp
2764
3413
  });
2765
- var SuppressionCreate = z11.object({
3414
+ var SuppressionCreate = z12.object({
2766
3415
  email: Email,
2767
- reason: z11.enum(["manual", "unsubscribed"]),
3416
+ reason: z12.enum(["manual", "unsubscribed"]),
2768
3417
  topic: Slug.nullable().optional(),
2769
- note: z11.string().max(1e3).optional()
3418
+ note: z12.string().max(1e3).optional()
2770
3419
  });
2771
3420
  var SuppressionListQuery = PageQuery.extend({
2772
- email: z11.string().min(1).max(254).optional(),
3421
+ email: z12.string().min(1).max(254).optional(),
2773
3422
  reason: SuppressionReason.optional(),
2774
3423
  topic: Slug.optional()
2775
3424
  });
2776
3425
 
2777
3426
  // src/webhooks.ts
2778
- import { z as z12 } from "zod";
3427
+ import { z as z13 } from "zod";
2779
3428
  var WEBHOOK_EVENT_TYPES = [
2780
3429
  "message.sent",
2781
3430
  "message.failed",
@@ -2819,40 +3468,47 @@ var WEBHOOK_EVENT_TYPES = [
2819
3468
  "template.updated",
2820
3469
  "template.deleted"
2821
3470
  ];
2822
- var WebhookEventType = z12.enum(WEBHOOK_EVENT_TYPES);
2823
- var MessageEventBase = z12.object({
3471
+ var WebhookEventType = z13.enum(WEBHOOK_EVENT_TYPES);
3472
+ var MessageEventBase = z13.object({
2824
3473
  message_id: Id,
2825
3474
  mailing_id: Id.nullable(),
2826
3475
  mailing_metadata: MailingMetadata.nullable(),
2827
3476
  recipient_id: Id.nullable(),
2828
3477
  contact_id: Id.nullable(),
2829
- external_id: z12.string().nullable(),
3478
+ external_id: z13.string().nullable(),
2830
3479
  email: Email,
2831
- subject: z12.string(),
3480
+ subject: z13.string(),
2832
3481
  topic: Slug.nullable(),
2833
- provider_message_id: z12.string().nullable(),
2834
- is_test: z12.boolean()
3482
+ provider_message_id: z13.string().nullable(),
3483
+ is_test: z13.boolean()
2835
3484
  });
2836
3485
  var MessageSentData = MessageEventBase.extend({
2837
3486
  /** The final HTML as sent, so the client can archive it beside its own records. */
2838
- html: z12.string(),
2839
- sent_at: Timestamp
3487
+ html: z13.string(),
3488
+ sent_at: Timestamp,
3489
+ /**
3490
+ * The choice each insertion made (see `Message.insertions`); absent or null
3491
+ * when the message had none. A `matched: false` entry is a value the
3492
+ * template has no choice for: the client sent something its form should
3493
+ * not offer.
3494
+ */
3495
+ insertions: InsertionChoices.nullish()
2840
3496
  });
2841
3497
  var MessageFailedData = MessageEventBase.extend({
2842
- error: z12.string(),
3498
+ error: z13.string(),
2843
3499
  /** False when the provider rejected permanently; the recipient will not be retried. */
2844
- retryable: z12.boolean(),
3500
+ retryable: z13.boolean(),
2845
3501
  failed_at: Timestamp
2846
3502
  });
2847
3503
  var UNSUBSCRIBE_SOURCES = ["hosted_page", "one_click", "api", "dashboard", "signup_form"];
2848
- var ContactUnsubscribedData = z12.object({
3504
+ var ContactUnsubscribedData = z13.object({
2849
3505
  contact_id: Id.nullable(),
2850
- external_id: z12.string().nullable(),
3506
+ external_id: z13.string().nullable(),
2851
3507
  email: Email,
2852
3508
  /** The topic unsubscribed from; null means every topic. */
2853
3509
  topic: Slug.nullable(),
2854
3510
  mailing_id: Id.nullable(),
2855
- source: z12.enum(UNSUBSCRIBE_SOURCES),
3511
+ source: z13.enum(UNSUBSCRIBE_SOURCES),
2856
3512
  unsubscribed_at: Timestamp
2857
3513
  });
2858
3514
  var ContactResubscribedData = ContactUnsubscribedData.omit({ unsubscribed_at: true }).extend({
@@ -2861,142 +3517,142 @@ var ContactResubscribedData = ContactUnsubscribedData.omit({ unsubscribed_at: tr
2861
3517
  * breaker undid a `bounced` block it judged false (a `contact.bounced` for the
2862
3518
  * same address and mailing went out before); `topic` is null.
2863
3519
  */
2864
- source: z12.enum([...UNSUBSCRIBE_SOURCES, "bounce_reverted"]),
3520
+ source: z13.enum([...UNSUBSCRIBE_SOURCES, "bounce_reverted"]),
2865
3521
  resubscribed_at: Timestamp
2866
3522
  });
2867
- var ContactBouncedData = z12.object({
3523
+ var ContactBouncedData = z13.object({
2868
3524
  contact_id: Id.nullable(),
2869
- external_id: z12.string().nullable(),
3525
+ external_id: z13.string().nullable(),
2870
3526
  email: Email,
2871
3527
  /** `bounced` for a hard bounce, `complained` for a spam complaint. */
2872
- reason: z12.enum(["bounced", "complained"]),
3528
+ reason: z13.enum(["bounced", "complained"]),
2873
3529
  message_id: Id.nullable(),
2874
- diagnostic: z12.string().nullable(),
3530
+ diagnostic: z13.string().nullable(),
2875
3531
  bounced_at: Timestamp
2876
3532
  });
2877
- var MailingFinishedData = z12.object({
3533
+ var MailingFinishedData = z13.object({
2878
3534
  mailing_id: Id,
2879
3535
  mailing_metadata: MailingMetadata,
2880
- status: z12.enum(["sent", "partially_failed", "cancelled"]),
3536
+ status: z13.enum(["sent", "partially_failed", "cancelled"]),
2881
3537
  counts: MailingCounts,
2882
3538
  finished_at: Timestamp
2883
3539
  });
2884
- var ContactSubscribedData = z12.object({
3540
+ var ContactSubscribedData = z13.object({
2885
3541
  contact_id: Id,
2886
- external_id: z12.string().nullable(),
3542
+ external_id: z13.string().nullable(),
2887
3543
  email: Email,
2888
- topics: z12.array(Slug),
2889
- source: z12.enum(["signup_form", "api", "dashboard", "automation"]),
3544
+ topics: z13.array(Slug),
3545
+ source: z13.enum(["signup_form", "api", "dashboard", "automation"]),
2890
3546
  signup_form_id: Id.nullable(),
2891
- signup_form_version: z12.number().int().min(1).nullable(),
3547
+ signup_form_version: z13.number().int().min(1).nullable(),
2892
3548
  subscribed_at: Timestamp
2893
3549
  });
2894
- var ImportFinishedData = z12.object({
3550
+ var ImportFinishedData = z13.object({
2895
3551
  import_id: Id,
2896
- status: z12.enum(["completed", "cancelled", "failed"]),
3552
+ status: z13.enum(["completed", "cancelled", "failed"]),
2897
3553
  result: ImportReport.nullable(),
2898
- error: z12.string().nullable(),
3554
+ error: z13.string().nullable(),
2899
3555
  finished_at: Timestamp
2900
3556
  });
2901
- var MailingScheduledData = z12.object({
3557
+ var MailingScheduledData = z13.object({
2902
3558
  mailing_id: Id,
2903
3559
  mailing_metadata: MailingMetadata,
2904
3560
  scheduled_at: Timestamp.nullable()
2905
3561
  });
2906
- var MailingStartedData = z12.object({
3562
+ var MailingStartedData = z13.object({
2907
3563
  mailing_id: Id,
2908
3564
  mailing_metadata: MailingMetadata,
2909
- trigger: z12.enum(["send", "schedule"]),
2910
- recipients: z12.number().int().min(0),
3565
+ trigger: z13.enum(["send", "schedule"]),
3566
+ recipients: z13.number().int().min(0),
2911
3567
  started_at: Timestamp
2912
3568
  });
2913
- var MailingScheduleFailedData = z12.object({
3569
+ var MailingScheduleFailedData = z13.object({
2914
3570
  mailing_id: Id,
2915
3571
  mailing_metadata: MailingMetadata,
2916
- code: z12.string(),
2917
- message: z12.string(),
3572
+ code: z13.string(),
3573
+ message: z13.string(),
2918
3574
  failed_at: Timestamp
2919
3575
  });
2920
- var MailingAbWinnerSelectedData = z12.object({
3576
+ var MailingAbWinnerSelectedData = z13.object({
2921
3577
  mailing_id: Id,
2922
3578
  mailing_metadata: MailingMetadata,
2923
- winner: z12.string(),
2924
- decided_by: z12.enum(["metric", "manual"]),
2925
- variants: z12.array(VariantAnalytics),
3579
+ winner: z13.string(),
3580
+ decided_by: z13.enum(["metric", "manual"]),
3581
+ variants: z13.array(VariantAnalytics),
2926
3582
  decided_at: Timestamp
2927
3583
  });
2928
- var ContactTaggedData = z12.object({
3584
+ var ContactTaggedData = z13.object({
2929
3585
  contact_id: Id,
2930
- external_id: z12.string().nullable(),
3586
+ external_id: z13.string().nullable(),
2931
3587
  email: Email,
2932
3588
  /** Only the tags the contact did not already have. */
2933
- tags: z12.array(Slug).min(1),
2934
- source: z12.enum(["api", "dashboard", "signup_form", "import", "automation"]),
3589
+ tags: z13.array(Slug).min(1),
3590
+ source: z13.enum(["api", "dashboard", "signup_form", "import", "automation"]),
2935
3591
  tagged_at: Timestamp
2936
3592
  });
2937
- var ContactUpdatedData = z12.object({
3593
+ var ContactUpdatedData = z13.object({
2938
3594
  contact_id: Id,
2939
- external_id: z12.string().nullable(),
3595
+ external_id: z13.string().nullable(),
2940
3596
  email: Email,
2941
- changed: z12.array(z12.string()).min(1),
3597
+ changed: z13.array(z13.string()).min(1),
2942
3598
  properties: Properties,
2943
- source: z12.enum(["api", "dashboard", "signup_form", "import", "automation"]),
3599
+ source: z13.enum(["api", "dashboard", "signup_form", "import", "automation"]),
2944
3600
  updated_at: Timestamp
2945
3601
  });
2946
- var AutomationRunEventBase = z12.object({
3602
+ var AutomationRunEventBase = z13.object({
2947
3603
  automation_id: Id,
2948
- automation_name: z12.string(),
3604
+ automation_name: z13.string(),
2949
3605
  run_id: Id,
2950
- version: z12.number().int().min(1),
3606
+ version: z13.number().int().min(1),
2951
3607
  contact_id: Id,
2952
- external_id: z12.string().nullable(),
3608
+ external_id: z13.string().nullable(),
2953
3609
  email: Email
2954
3610
  });
2955
3611
  var AutomationRunStartedData = AutomationRunEventBase.extend({
2956
3612
  /** What let them in: a trigger kind, or `manual` for an explicit enrolment. */
2957
- trigger_kind: z12.string(),
3613
+ trigger_kind: z13.string(),
2958
3614
  entered_at: Timestamp
2959
3615
  });
2960
3616
  var AutomationRunCompletedData = AutomationRunEventBase.extend({
2961
- last_step_id: z12.string().nullable(),
3617
+ last_step_id: z13.string().nullable(),
2962
3618
  completed_at: Timestamp
2963
3619
  });
2964
3620
  var AutomationRunExitedData = AutomationRunEventBase.extend({
2965
3621
  reason: AutomationEndReason,
2966
- step_id: z12.string().nullable(),
3622
+ step_id: z13.string().nullable(),
2967
3623
  exited_at: Timestamp
2968
3624
  });
2969
3625
  var AutomationRunFailedData = AutomationRunEventBase.extend({
2970
- reason: z12.string(),
2971
- step_id: z12.string().nullable(),
3626
+ reason: z13.string(),
3627
+ step_id: z13.string().nullable(),
2972
3628
  failed_at: Timestamp
2973
3629
  });
2974
3630
  var AutomationWebhookData = AutomationRunEventBase.extend({
2975
- step_id: z12.string(),
3631
+ step_id: z13.string(),
2976
3632
  /** The step's own label, when it was given one, so a receiver can branch on a name. */
2977
- step_label: z12.string().nullable(),
3633
+ step_label: z13.string().nullable(),
2978
3634
  /** Whatever the step carries, verbatim. Null when it carries nothing. */
2979
3635
  payload: JsonValue.nullable(),
2980
3636
  fired_at: Timestamp
2981
3637
  });
2982
- var TemplateEventBase = z12.object({
3638
+ var TemplateEventBase = z13.object({
2983
3639
  template_id: Id,
2984
- name: z12.string().min(1).max(200),
2985
- version: z12.number().int().min(1),
2986
- source: z12.enum(["api", "dashboard", "mjml_import"])
3640
+ name: z13.string().min(1).max(200),
3641
+ version: z13.number().int().min(1),
3642
+ source: z13.enum(["api", "dashboard", "mjml_import"])
2987
3643
  });
2988
3644
  var TemplateCreatedData = TemplateEventBase.extend({ created_at: Timestamp });
2989
- var TemplateUpdatedData = TemplateEventBase.extend({ archived: z12.boolean(), updated_at: Timestamp });
3645
+ var TemplateUpdatedData = TemplateEventBase.extend({ archived: z13.boolean(), updated_at: Timestamp });
2990
3646
  var TemplateDeletedData = TemplateEventBase.extend({ deleted_at: Timestamp });
2991
- var envelope = (type, data) => z12.object({
3647
+ var envelope = (type, data) => z13.object({
2992
3648
  /** Unique per event: a receiver deduplicates on it (deliveries may repeat). */
2993
3649
  id: Id,
2994
- type: z12.literal(type),
3650
+ type: z13.literal(type),
2995
3651
  created_at: Timestamp,
2996
3652
  workspace_id: Id,
2997
3653
  data
2998
3654
  });
2999
- var WebhookEvent = z12.discriminatedUnion("type", [
3655
+ var WebhookEvent = z13.discriminatedUnion("type", [
3000
3656
  envelope("message.sent", MessageSentData),
3001
3657
  envelope("message.failed", MessageFailedData),
3002
3658
  envelope("contact.unsubscribed", ContactUnsubscribedData),
@@ -3020,16 +3676,16 @@ var WebhookEvent = z12.discriminatedUnion("type", [
3020
3676
  envelope("template.updated", TemplateUpdatedData),
3021
3677
  envelope("template.deleted", TemplateDeletedData)
3022
3678
  ]);
3023
- var WebhookEndpoint = z12.object({
3679
+ var WebhookEndpoint = z13.object({
3024
3680
  id: Id,
3025
- url: z12.string().url(),
3026
- description: z12.string().max(500).nullable(),
3027
- events: z12.array(WebhookEventType).min(1),
3028
- enabled: z12.boolean(),
3681
+ url: z13.string().url(),
3682
+ description: z13.string().max(500).nullable(),
3683
+ events: z13.array(WebhookEventType).min(1),
3684
+ enabled: z13.boolean(),
3029
3685
  created_at: Timestamp,
3030
3686
  updated_at: Timestamp
3031
3687
  });
3032
- var WebhookUrl = z12.string().url().refine((u) => {
3688
+ var WebhookUrl = z13.string().url().refine((u) => {
3033
3689
  try {
3034
3690
  const url = new URL(u);
3035
3691
  if (url.protocol === "https:") return true;
@@ -3038,35 +3694,35 @@ var WebhookUrl = z12.string().url().refine((u) => {
3038
3694
  return false;
3039
3695
  }
3040
3696
  }, "must be https (http only for localhost)");
3041
- var WebhookEndpointCreate = z12.object({
3697
+ var WebhookEndpointCreate = z13.object({
3042
3698
  url: WebhookUrl,
3043
- description: z12.string().max(500).optional(),
3044
- events: z12.array(WebhookEventType).min(1),
3045
- enabled: z12.boolean().optional()
3699
+ description: z13.string().max(500).optional(),
3700
+ events: z13.array(WebhookEventType).min(1),
3701
+ enabled: z13.boolean().optional()
3046
3702
  });
3047
- var WebhookEndpointUpdate = z12.object({
3703
+ var WebhookEndpointUpdate = z13.object({
3048
3704
  url: WebhookUrl,
3049
- description: z12.string().max(500).nullable(),
3050
- events: z12.array(WebhookEventType).min(1),
3051
- enabled: z12.boolean()
3705
+ description: z13.string().max(500).nullable(),
3706
+ events: z13.array(WebhookEventType).min(1),
3707
+ enabled: z13.boolean()
3052
3708
  }).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
3053
- var WebhookEndpointWithSecret = z12.object({
3709
+ var WebhookEndpointWithSecret = z13.object({
3054
3710
  endpoint: WebhookEndpoint,
3055
- secret: z12.string().min(32)
3711
+ secret: z13.string().min(32)
3056
3712
  });
3057
3713
  var WEBHOOK_DELIVERY_STATUSES = ["pending", "succeeded", "failed"];
3058
- var WebhookDeliveryStatus = z12.enum(WEBHOOK_DELIVERY_STATUSES);
3714
+ var WebhookDeliveryStatus = z13.enum(WEBHOOK_DELIVERY_STATUSES);
3059
3715
  var WEBHOOK_MAX_ATTEMPTS = 8;
3060
3716
  var WEBHOOK_RETRY_DELAYS_SECONDS = [30, 120, 600, 1800, 3600, 7200, 21600];
3061
- var WebhookDelivery = z12.object({
3717
+ var WebhookDelivery = z13.object({
3062
3718
  id: Id,
3063
3719
  endpoint_id: Id,
3064
3720
  event_id: Id,
3065
3721
  event_type: WebhookEventType,
3066
3722
  status: WebhookDeliveryStatus,
3067
- attempts: z12.number().int().min(0),
3068
- last_status_code: z12.number().int().min(100).max(599).nullable(),
3069
- last_error: z12.string().nullable(),
3723
+ attempts: z13.number().int().min(0),
3724
+ last_status_code: z13.number().int().min(100).max(599).nullable(),
3725
+ last_error: z13.string().nullable(),
3070
3726
  next_attempt_at: Timestamp.nullable(),
3071
3727
  delivered_at: Timestamp.nullable(),
3072
3728
  created_at: Timestamp
@@ -3075,7 +3731,7 @@ var WebhookDeliveryListQuery = PageQuery.extend({
3075
3731
  status: WebhookDeliveryStatus.optional(),
3076
3732
  event_type: WebhookEventType.optional()
3077
3733
  });
3078
- var WebhookDeliveryParams = z12.object({ id: Id, delivery_id: Id });
3734
+ var WebhookDeliveryParams = z13.object({ id: Id, delivery_id: Id });
3079
3735
 
3080
3736
  // src/webhook-signing.ts
3081
3737
  var WEBHOOK_SIGNATURE_HEADER = "x-mail-signature";
@@ -3142,79 +3798,79 @@ async function verifyWebhook(input) {
3142
3798
  }
3143
3799
 
3144
3800
  // src/billing.ts
3145
- import { z as z13 } from "zod";
3801
+ import { z as z14 } from "zod";
3146
3802
  var PLAN_IDS = ["free", "starter", "growth", "design_partner"];
3147
- var PlanId = z13.enum(PLAN_IDS);
3148
- var PlanLimits = z13.object({
3149
- monthly_messages: z13.number().int().min(0).nullable(),
3150
- contacts: z13.number().int().min(0).nullable(),
3151
- members: z13.number().int().min(1).nullable(),
3152
- providers: z13.number().int().min(1).nullable(),
3153
- webhook_endpoints: z13.number().int().min(0).nullable(),
3803
+ var PlanId = z14.enum(PLAN_IDS);
3804
+ var PlanLimits = z14.object({
3805
+ monthly_messages: z14.number().int().min(0).nullable(),
3806
+ contacts: z14.number().int().min(0).nullable(),
3807
+ members: z14.number().int().min(1).nullable(),
3808
+ providers: z14.number().int().min(1).nullable(),
3809
+ webhook_endpoints: z14.number().int().min(0).nullable(),
3154
3810
  /** Automations that may be live (active or paused) at once (A1). */
3155
- active_automations: z13.number().int().min(0).nullable(),
3811
+ active_automations: z14.number().int().min(0).nullable(),
3156
3812
  /** Steps one automation may hold. Null is the flow's own cap, not unlimited. */
3157
- automation_steps: z13.number().int().min(1).nullable()
3813
+ automation_steps: z14.number().int().min(1).nullable()
3158
3814
  });
3159
- var PlanFeatures = z13.object({
3815
+ var PlanFeatures = z14.object({
3160
3816
  /** A/B tests of subject and content on a mailing (S4). */
3161
- ab_testing: z13.boolean(),
3817
+ ab_testing: z14.boolean(),
3162
3818
  /** Open and click tracking, still off until a workspace turns it on (`settings.tracking_enabled`). */
3163
- tracking: z13.boolean(),
3819
+ tracking: z14.boolean(),
3164
3820
  /** Sending and hosting pages on the workspace's own domain (a later phase). */
3165
- custom_domains: z13.boolean(),
3821
+ custom_domains: z14.boolean(),
3166
3822
  /** Automations: flows a person walks through step by step (A1). */
3167
- automations: z13.boolean()
3823
+ automations: z14.boolean()
3168
3824
  });
3169
3825
  var PLAN_FEATURES = ["ab_testing", "tracking", "custom_domains", "automations"];
3170
- var Plan = z13.object({
3826
+ var Plan = z14.object({
3171
3827
  id: PlanId,
3172
- name: z13.string().min(1),
3828
+ name: z14.string().min(1),
3173
3829
  /** Price in the smallest currency unit per month; null for plans not sold. */
3174
- monthly_price_cents: z13.number().int().min(0).nullable(),
3175
- currency: z13.string().length(3),
3830
+ monthly_price_cents: z14.number().int().min(0).nullable(),
3831
+ currency: z14.string().length(3),
3176
3832
  limits: PlanLimits,
3177
3833
  features: PlanFeatures
3178
3834
  });
3179
- var CatalogPlan = Plan.extend({ sellable: z13.boolean() });
3180
- var Subscription = z13.object({
3835
+ var CatalogPlan = Plan.extend({ sellable: z14.boolean() });
3836
+ var Subscription = z14.object({
3181
3837
  workspace_id: Id,
3182
3838
  plan: PlanId,
3183
- status: z13.enum(["active", "trialing", "past_due", "cancelled"]),
3839
+ status: z14.enum(["active", "trialing", "past_due", "cancelled"]),
3184
3840
  current_period_start: Timestamp,
3185
3841
  current_period_end: Timestamp,
3186
- cancel_at_period_end: z13.boolean(),
3842
+ cancel_at_period_end: z14.boolean(),
3187
3843
  /**
3188
3844
  * A design partner outside billing (plan `design_partner`, no limits, no
3189
3845
  * checkout). Set only by an operator, never through the API.
3190
3846
  */
3191
- billing_exempt: z13.boolean()
3847
+ billing_exempt: z14.boolean()
3192
3848
  });
3193
3849
  var USAGE_METRICS = ["messages", "contacts", "members", "providers", "webhook_endpoints", "active_automations"];
3194
- var UsageMetric = z13.enum(USAGE_METRICS);
3850
+ var UsageMetric = z14.enum(USAGE_METRICS);
3195
3851
  var USAGE_WARNING_RATIO = 0.8;
3196
- var UsageWarning = z13.object({
3852
+ var UsageWarning = z14.object({
3197
3853
  metric: UsageMetric,
3198
- used: z13.number().int().min(0),
3199
- limit: z13.number().int().min(0),
3854
+ used: z14.number().int().min(0),
3855
+ limit: z14.number().int().min(0),
3200
3856
  /** `approaching`: at or above USAGE_WARNING_RATIO of the limit; `reached`: at or above the limit. */
3201
- level: z13.enum(["approaching", "reached"])
3857
+ level: z14.enum(["approaching", "reached"])
3202
3858
  });
3203
- var Usage = z13.object({
3859
+ var Usage = z14.object({
3204
3860
  plan: PlanId,
3205
3861
  period_start: Timestamp,
3206
3862
  period_end: Timestamp,
3207
- metrics: z13.record(UsageMetric, z13.object({ used: z13.number().int().min(0), limit: z13.number().int().min(0).nullable() })),
3208
- warnings: z13.array(UsageWarning)
3863
+ metrics: z14.record(UsageMetric, z14.object({ used: z14.number().int().min(0), limit: z14.number().int().min(0).nullable() })),
3864
+ warnings: z14.array(UsageWarning)
3209
3865
  });
3210
- var CheckoutRequest = z13.object({
3211
- plan: z13.enum(["starter", "growth"]),
3212
- success_url: z13.string().url(),
3213
- cancel_url: z13.string().url()
3866
+ var CheckoutRequest = z14.object({
3867
+ plan: z14.enum(["starter", "growth"]),
3868
+ success_url: z14.string().url(),
3869
+ cancel_url: z14.string().url()
3214
3870
  });
3215
- var CheckoutSession = z13.object({ url: z13.string().url() });
3216
- var PortalRequest = z13.object({ return_url: z13.string().url() });
3217
- var PortalSession = z13.object({ url: z13.string().url() });
3871
+ var CheckoutSession = z14.object({ url: z14.string().url() });
3872
+ var PortalRequest = z14.object({ return_url: z14.string().url() });
3873
+ var PortalSession = z14.object({ url: z14.string().url() });
3218
3874
  var USAGE_WARNING_HEADER = "x-mail-usage-warning";
3219
3875
  function formatUsageWarningHeader(warnings) {
3220
3876
  if (warnings.length === 0) return null;
@@ -3234,7 +3890,7 @@ function parseUsageWarningHeader(value) {
3234
3890
  }
3235
3891
 
3236
3892
  // src/routes.ts
3237
- import { z as z14 } from "zod";
3893
+ import { z as z15 } from "zod";
3238
3894
  var list = (item) => page(item);
3239
3895
  var foundationRoutes = {
3240
3896
  "workspaces.create": {
@@ -3442,7 +4098,7 @@ var templateRoutes = {
3442
4098
  path: "/v1/templates/:id/export",
3443
4099
  params: IdParams,
3444
4100
  query: TemplateExportQuery,
3445
- response: z14.string(),
4101
+ response: z15.string(),
3446
4102
  responseType: "text",
3447
4103
  status: 200,
3448
4104
  access: "read",
@@ -3766,7 +4422,7 @@ var sendingRoutes = {
3766
4422
  path: "/v1/mailings/:id/export",
3767
4423
  params: IdParams,
3768
4424
  query: MailingExportQuery,
3769
- response: z14.string(),
4425
+ response: z15.string(),
3770
4426
  responseType: "text",
3771
4427
  status: 200,
3772
4428
  access: "read",
@@ -3999,7 +4655,7 @@ var platformRoutes = {
3999
4655
  "contactProperties.list": {
4000
4656
  method: "GET",
4001
4657
  path: "/v1/contact-properties",
4002
- response: z14.object({ data: z14.array(ContactPropertyDefinition) }),
4658
+ response: z15.object({ data: z15.array(ContactPropertyDefinition) }),
4003
4659
  status: 200,
4004
4660
  access: "read",
4005
4661
  phase: "S4"
@@ -4298,7 +4954,7 @@ var billingRoutes = {
4298
4954
  "billing.plans": {
4299
4955
  method: "GET",
4300
4956
  path: "/v1/billing/plans",
4301
- response: z14.object({ data: z14.array(CatalogPlan) }),
4957
+ response: z15.object({ data: z15.array(CatalogPlan) }),
4302
4958
  status: 200,
4303
4959
  access: "read",
4304
4960
  phase: "S5"
@@ -4440,7 +5096,7 @@ var automationRoutes = {
4440
5096
  method: "POST",
4441
5097
  path: "/v1/automations/:id/pause",
4442
5098
  params: IdParams,
4443
- body: z14.object({}).strict(),
5099
+ body: z15.object({}).strict(),
4444
5100
  response: Automation,
4445
5101
  status: 200,
4446
5102
  access: "write",
@@ -4450,7 +5106,7 @@ var automationRoutes = {
4450
5106
  method: "POST",
4451
5107
  path: "/v1/automations/:id/resume",
4452
5108
  params: IdParams,
4453
- body: z14.object({}).strict(),
5109
+ body: z15.object({}).strict(),
4454
5110
  response: Automation,
4455
5111
  status: 200,
4456
5112
  access: "write",
@@ -4460,7 +5116,7 @@ var automationRoutes = {
4460
5116
  method: "POST",
4461
5117
  path: "/v1/automations/:id/archive",
4462
5118
  params: IdParams,
4463
- body: z14.object({}).strict(),
5119
+ body: z15.object({}).strict(),
4464
5120
  response: Automation,
4465
5121
  status: 200,
4466
5122
  access: "write",
@@ -4594,13 +5250,13 @@ function matchRoute(method, pathname) {
4594
5250
  for (const [id, route] of Object.entries(routes)) {
4595
5251
  if (route.method !== method) continue;
4596
5252
  const names = [];
4597
- const pattern = new RegExp(
5253
+ const pattern2 = new RegExp(
4598
5254
  "^" + route.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:([a-z_]+)/g, (_, name) => {
4599
5255
  names.push(name);
4600
5256
  return "([^/]+)";
4601
5257
  }) + "$"
4602
5258
  );
4603
- const match = pattern.exec(pathname);
5259
+ const match = pattern2.exec(pathname);
4604
5260
  if (!match) continue;
4605
5261
  const params = {};
4606
5262
  names.forEach((name, i) => {
@@ -4766,6 +5422,8 @@ export {
4766
5422
  IMPORT_SKIP_REASONS,
4767
5423
  IMPORT_STATUSES,
4768
5424
  IMPORT_TERMINAL_STATUSES,
5425
+ INSERTION_PROBLEM_REASONS,
5426
+ INSERTION_TEXT_SLOTS,
4769
5427
  INVITE_REFUSAL_REASONS,
4770
5428
  INVITE_STATUSES,
4771
5429
  Id,
@@ -4783,6 +5441,13 @@ export {
4783
5441
  ImportStatus,
4784
5442
  ImportWarning,
4785
5443
  ImportedAsset,
5444
+ Insertion,
5445
+ InsertionChoice,
5446
+ InsertionChoiceKey,
5447
+ InsertionChoices,
5448
+ InsertionName,
5449
+ InsertionText,
5450
+ Insertions,
4786
5451
  Invite,
4787
5452
  InviteAccept,
4788
5453
  InviteAccepted,
@@ -4810,6 +5475,9 @@ export {
4810
5475
  MAX_IMPORT_BYTES,
4811
5476
  MAX_IMPORT_ROWS,
4812
5477
  MAX_IMPORT_URL_LENGTH,
5478
+ MAX_INSERTIONS,
5479
+ MAX_INSERTION_CHOICES,
5480
+ MAX_INSERTION_TEXT_LENGTH,
4813
5481
  MAX_INVITE_TTL_DAYS,
4814
5482
  MAX_IN_LIST,
4815
5483
  MAX_LETTER_RECIPIENTS,
@@ -4882,6 +5550,8 @@ export {
4882
5550
  Ok,
4883
5551
  PLAN_FEATURES,
4884
5552
  PLAN_IDS,
5553
+ PREVIEW_EMPTY,
5554
+ PREVIEW_FILLED,
4885
5555
  PROVIDER_KINDS,
4886
5556
  PageQuery,
4887
5557
  Plan,
@@ -4982,6 +5652,8 @@ export {
4982
5652
  TopicCreate,
4983
5653
  TopicUpdate,
4984
5654
  TrackingSettings,
5655
+ TranslationInsertion,
5656
+ TranslationInsertions,
4985
5657
  UNSUBSCRIBE_PATH_PREFIX,
4986
5658
  UNSUBSCRIBE_SOURCES,
4987
5659
  USAGE_METRICS,
@@ -5023,7 +5695,10 @@ export {
5023
5695
  WorkspaceSettings,
5024
5696
  WorkspaceUpdate,
5025
5697
  acceptsIdempotencyKey,
5698
+ addInsertion,
5699
+ addInsertionChoice,
5026
5700
  addLanguageDraft,
5701
+ alignTranslationInsertions,
5027
5702
  alwaysWaits,
5028
5703
  automationFilterFamily,
5029
5704
  automationRoutes,
@@ -5032,15 +5707,27 @@ export {
5032
5707
  buildTemplatePatch,
5033
5708
  canTransition,
5034
5709
  canonicalLocale,
5710
+ choiceKeyProblem,
5711
+ chooseInsertion,
5035
5712
  cycles,
5713
+ defaultPreviewPick,
5036
5714
  defaultSettings,
5715
+ demoteForSave,
5716
+ demotedBySave,
5717
+ describeMissingTexts,
5037
5718
  draftDocument,
5719
+ draftInsertionProblems,
5038
5720
  draftsDirty,
5039
5721
  draftsFromTemplate,
5040
5722
  draftsPatch,
5723
+ editInsertionText,
5041
5724
  editLanguageDraft,
5725
+ editMainInsertionText,
5726
+ editMainInsertions,
5727
+ effectivePreviewPicks,
5042
5728
  emptyFlow,
5043
5729
  errorBody,
5730
+ escapeMergeHtml,
5044
5731
  exportFilename,
5045
5732
  filterConditions,
5046
5733
  filterDepth,
@@ -5050,30 +5737,65 @@ export {
5050
5737
  formatExportWarningsHeader,
5051
5738
  formatUsageWarningHeader,
5052
5739
  foundationRoutes,
5740
+ insertionByProblem,
5741
+ insertionChoices,
5742
+ insertionCoverage,
5743
+ insertionInputs,
5744
+ insertionNameProblem,
5745
+ insertionProblems,
5746
+ insertionRefusalHint,
5747
+ insertionSlotLabel,
5748
+ insertionTextSlots,
5749
+ insertionTokens,
5750
+ insertionValue,
5053
5751
  inviteRoutes,
5054
5752
  isDayEntirelyPast,
5055
5753
  isPublishable,
5056
5754
  isRetryableError,
5057
5755
  keepLanguageDocument,
5756
+ languageInsertions,
5058
5757
  languageName,
5059
5758
  languageTabs,
5759
+ mainInsertionText,
5060
5760
  matchLocale,
5061
5761
  matchRoute,
5762
+ missingInsertionTexts,
5062
5763
  missingRequiredMergeFields,
5764
+ normalizeChoiceKey,
5063
5765
  nowSeconds,
5064
5766
  operationsWithAccess,
5767
+ ownEntry,
5065
5768
  page,
5066
5769
  parseExportWarningsHeader,
5067
5770
  parseUsageWarningHeader,
5771
+ personalizeHtml,
5772
+ personalizeText,
5773
+ placementProblems,
5068
5774
  platformRoutes,
5775
+ previewControls,
5776
+ previewInsertions,
5777
+ previewPersonalization,
5069
5778
  primaryLanguage,
5070
5779
  reachable,
5780
+ readyWithoutInsertions,
5071
5781
  readyWithoutSubject,
5072
5782
  rebaseDrafts,
5783
+ removeInsertion,
5784
+ removeInsertionChoice,
5073
5785
  removeLanguageDraft,
5786
+ renameInsertion,
5787
+ renameInsertionChecked,
5788
+ renameInsertionChoice,
5789
+ renameInsertionChoiceChecked,
5790
+ resolveInsertions,
5074
5791
  restorePatch,
5075
5792
  routes,
5793
+ sameJson,
5794
+ sampleMergeValue,
5795
+ saveDemotions,
5076
5796
  sendingRoutes,
5797
+ setInsertionBy,
5798
+ setInsertionFilled,
5077
5799
  setLanguageStatus,
5078
5800
  signWebhook,
5079
5801
  statusForError,
@@ -5084,7 +5806,10 @@ export {
5084
5806
  testGroupSize,
5085
5807
  tightLoops,
5086
5808
  timingSafeEqualString,
5809
+ translationInsertionProblems,
5810
+ translationTextFromInput,
5087
5811
  triggerKey,
5812
+ unplacedInsertions,
5088
5813
  validateAutomation,
5089
5814
  verifyWebhook
5090
5815
  };