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