@marlinjai/mail-contract 0.6.1 → 0.8.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 +16533 -2594
- package/dist/index.d.ts +16533 -2594
- package/dist/index.js +1063 -763
- package/dist/index.mjs +1036 -763
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -145,13 +145,13 @@ var API_VERSION_PREFIX = "/v1";
|
|
|
145
145
|
var HEALTH_PATH = "/healthz";
|
|
146
146
|
|
|
147
147
|
// src/workspace.ts
|
|
148
|
-
import { z as
|
|
148
|
+
import { z as z10 } from "zod";
|
|
149
149
|
|
|
150
150
|
// src/automations.ts
|
|
151
|
-
import { z as
|
|
151
|
+
import { z as z9 } from "zod";
|
|
152
152
|
|
|
153
153
|
// src/templates.ts
|
|
154
|
-
import { z as
|
|
154
|
+
import { z as z5 } from "zod";
|
|
155
155
|
|
|
156
156
|
// src/insertions.ts
|
|
157
157
|
import { z as z3 } from "zod";
|
|
@@ -442,22 +442,133 @@ function sampleMergeValue(values) {
|
|
|
442
442
|
};
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
// src/template-inputs.ts
|
|
446
|
+
import { z as z4 } from "zod";
|
|
447
|
+
var MAX_TEMPLATE_INPUTS = 50;
|
|
448
|
+
var MAX_TEMPLATE_INPUT_VALUES = 30;
|
|
449
|
+
var MAX_TEMPLATE_INPUT_LABEL_LENGTH = 80;
|
|
450
|
+
var MAX_TEMPLATE_INPUT_NOTE_LENGTH = 300;
|
|
451
|
+
var MAX_TEMPLATE_INPUT_EXAMPLE_LENGTH = 500;
|
|
452
|
+
var MAX_TEMPLATE_INPUTS_APP_LENGTH = 80;
|
|
453
|
+
var MAX_TEMPLATE_INPUTS_SEEN = 200;
|
|
454
|
+
var SERVICE_FILLED_MERGE_FIELDS = ["email", "unsubscribe_url"];
|
|
455
|
+
var line = (max) => z4.string().min(1).max(max).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters").refine((s) => s.trim() !== "", "not blank");
|
|
456
|
+
var label = line(MAX_TEMPLATE_INPUT_LABEL_LENGTH);
|
|
457
|
+
var note = line(MAX_TEMPLATE_INPUT_NOTE_LENGTH);
|
|
458
|
+
var TemplateInputName = InsertionName;
|
|
459
|
+
var TemplateInputSource = z4.discriminatedUnion("kind", [
|
|
460
|
+
z4.object({ kind: z4.literal("form_field"), form: label, field: label, note: note.optional() }),
|
|
461
|
+
z4.object({ kind: z4.literal("derived"), note })
|
|
462
|
+
]);
|
|
463
|
+
var TemplateInputValue = z4.object({
|
|
464
|
+
/** The key the app sends, in an insertion choice's syntax, so a row "When it is venue" matches it exactly. */
|
|
465
|
+
value: InsertionChoiceKey,
|
|
466
|
+
label
|
|
467
|
+
});
|
|
468
|
+
var HttpUrl = z4.string().max(MAX_TEMPLATE_INPUT_EXAMPLE_LENGTH).url().refine((u) => {
|
|
469
|
+
try {
|
|
470
|
+
const { protocol } = new URL(u);
|
|
471
|
+
return protocol === "https:" || protocol === "http:";
|
|
472
|
+
} catch {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
}, "an http or https address");
|
|
476
|
+
var common = {
|
|
477
|
+
/** What an author sees: "Contact reason", "First name". */
|
|
478
|
+
label,
|
|
479
|
+
source: TemplateInputSource,
|
|
480
|
+
/** The app may send it empty or not at all (a name nobody typed). Absent means it is always sent. */
|
|
481
|
+
optional: z4.boolean().optional()
|
|
482
|
+
};
|
|
483
|
+
var TemplateInput = z4.discriminatedUnion("kind", [
|
|
484
|
+
z4.object({ kind: z4.literal("text"), ...common, example: z4.string().max(MAX_TEMPLATE_INPUT_EXAMPLE_LENGTH).optional() }),
|
|
485
|
+
z4.object({ kind: z4.literal("url"), ...common, example: HttpUrl.optional() }),
|
|
486
|
+
z4.object({ kind: z4.literal("boolean"), ...common, example: z4.boolean().optional() }),
|
|
487
|
+
z4.object({
|
|
488
|
+
kind: z4.literal("one_of"),
|
|
489
|
+
...common,
|
|
490
|
+
values: z4.array(TemplateInputValue).min(1).max(MAX_TEMPLATE_INPUT_VALUES),
|
|
491
|
+
example: InsertionChoiceKey.optional()
|
|
492
|
+
})
|
|
493
|
+
]);
|
|
494
|
+
var TEMPLATE_INPUT_KINDS = ["text", "url", "boolean", "one_of"];
|
|
495
|
+
var TemplateInputMap = z4.record(TemplateInputName, TemplateInput).refine((m) => Object.keys(m).length <= MAX_TEMPLATE_INPUTS, `at most ${MAX_TEMPLATE_INPUTS} inputs`);
|
|
496
|
+
var TemplateInputsDeclaration = z4.object({
|
|
497
|
+
app: line(MAX_TEMPLATE_INPUTS_APP_LENGTH),
|
|
498
|
+
app_version: line(MAX_TEMPLATE_INPUTS_APP_LENGTH).optional(),
|
|
499
|
+
inputs: TemplateInputMap
|
|
500
|
+
});
|
|
501
|
+
var TemplateInputs = z4.object({
|
|
502
|
+
app: z4.string(),
|
|
503
|
+
app_version: z4.string().nullable(),
|
|
504
|
+
inputs: TemplateInputMap,
|
|
505
|
+
declared_by: z4.object({ api_key_id: Id, api_key_name: z4.string() }),
|
|
506
|
+
declared_at: Timestamp
|
|
507
|
+
});
|
|
508
|
+
var TemplateInputSeen = z4.object({
|
|
509
|
+
count: z4.number().int().min(1),
|
|
510
|
+
first_seen_at: Timestamp,
|
|
511
|
+
last_seen_at: Timestamp
|
|
512
|
+
});
|
|
513
|
+
var TemplateInputsSeen = z4.record(TemplateInputName, TemplateInputSeen);
|
|
514
|
+
var TemplateInputsDeclared = z4.object({
|
|
515
|
+
template_id: Id,
|
|
516
|
+
inputs: TemplateInputs,
|
|
517
|
+
/** False when the stored declaration already said exactly this (a redeploy of the same commit). */
|
|
518
|
+
changed: z4.boolean()
|
|
519
|
+
});
|
|
520
|
+
var TEMPLATE_INPUT_PROBLEM_REASONS = [
|
|
521
|
+
/** `email` or `unsubscribe_url`: the service fills it, a client's value is never read. */
|
|
522
|
+
"input_reserved",
|
|
523
|
+
/** A `one_of` input lists the same value twice. */
|
|
524
|
+
"input_values",
|
|
525
|
+
/** A `one_of` input's example is not one of its values. */
|
|
526
|
+
"input_example"
|
|
527
|
+
];
|
|
528
|
+
function templateInputProblems(inputs) {
|
|
529
|
+
const problems = [];
|
|
530
|
+
for (const [name, input] of Object.entries(inputs)) {
|
|
531
|
+
if (SERVICE_FILLED_MERGE_FIELDS.includes(name)) {
|
|
532
|
+
problems.push({ reason: "input_reserved", path: name, message: `"${name}" is filled by the service, never by the app; leave it out of the declaration` });
|
|
533
|
+
}
|
|
534
|
+
if (input.kind !== "one_of") continue;
|
|
535
|
+
const seen = /* @__PURE__ */ new Set();
|
|
536
|
+
input.values.forEach((v, i) => {
|
|
537
|
+
if (seen.has(v.value)) {
|
|
538
|
+
problems.push({ reason: "input_values", path: `${name}.values.${i}`, message: `"${v.value}" is listed twice` });
|
|
539
|
+
}
|
|
540
|
+
seen.add(v.value);
|
|
541
|
+
});
|
|
542
|
+
if (input.example !== void 0 && !seen.has(input.example)) {
|
|
543
|
+
problems.push({ reason: "input_example", path: `${name}.example`, message: `the example "${input.example}" is not one of the values` });
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return problems;
|
|
547
|
+
}
|
|
548
|
+
function templateInputDrift(inputs, seen) {
|
|
549
|
+
const declared = inputs ? Object.keys(inputs.inputs) : [];
|
|
550
|
+
return {
|
|
551
|
+
declared_not_seen: declared.filter((name) => ownEntry(seen, name) === void 0).sort(),
|
|
552
|
+
seen_not_declared: Object.keys(seen).filter((name) => ownEntry(inputs?.inputs, name) === void 0).sort()
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
445
556
|
// src/templates.ts
|
|
446
557
|
var DOCUMENT_SCHEMA_VERSIONS = ["1.0", "1.1"];
|
|
447
|
-
var DocumentSchemaVersion =
|
|
448
|
-
var TemplateDocument =
|
|
558
|
+
var DocumentSchemaVersion = z5.enum(DOCUMENT_SCHEMA_VERSIONS);
|
|
559
|
+
var TemplateDocument = z5.object({
|
|
449
560
|
version: DocumentSchemaVersion,
|
|
450
|
-
metadata:
|
|
451
|
-
sections:
|
|
561
|
+
metadata: z5.record(z5.unknown()),
|
|
562
|
+
sections: z5.array(z5.record(z5.unknown()))
|
|
452
563
|
}).passthrough();
|
|
453
564
|
var MAX_DOCUMENT_BYTES = 1e6;
|
|
454
565
|
var MAX_SUBJECT_LENGTH = 998;
|
|
455
566
|
var MAX_PREHEADER_LENGTH = 500;
|
|
456
|
-
var TemplateSubject =
|
|
457
|
-
var TemplatePreheader =
|
|
567
|
+
var TemplateSubject = z5.string().max(MAX_SUBJECT_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
|
|
568
|
+
var TemplatePreheader = z5.string().max(MAX_PREHEADER_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
|
|
458
569
|
var TEMPLATE_LANGUAGE_STATUSES = ["draft", "ready"];
|
|
459
|
-
var TemplateLanguageStatus =
|
|
460
|
-
var TemplateTranslation =
|
|
570
|
+
var TemplateLanguageStatus = z5.enum(TEMPLATE_LANGUAGE_STATUSES);
|
|
571
|
+
var TemplateTranslation = z5.object({
|
|
461
572
|
subject: TemplateSubject.nullable(),
|
|
462
573
|
preheader: TemplatePreheader.nullable(),
|
|
463
574
|
document: TemplateDocument,
|
|
@@ -475,11 +586,11 @@ var TemplateTranslation = z4.object({
|
|
|
475
586
|
insertions: TranslationInsertions.optional()
|
|
476
587
|
});
|
|
477
588
|
var MAX_TEMPLATE_TRANSLATIONS = 19;
|
|
478
|
-
var TemplateTranslations =
|
|
479
|
-
var Template =
|
|
589
|
+
var TemplateTranslations = z5.record(LocaleTag, TemplateTranslation).refine((t) => Object.keys(t).length <= MAX_TEMPLATE_TRANSLATIONS, `at most ${MAX_TEMPLATE_TRANSLATIONS} language versions`);
|
|
590
|
+
var Template = z5.object({
|
|
480
591
|
id: Id,
|
|
481
|
-
name:
|
|
482
|
-
description:
|
|
592
|
+
name: z5.string().min(1).max(200),
|
|
593
|
+
description: z5.string().max(2e3).nullable(),
|
|
483
594
|
/**
|
|
484
595
|
* The main language: what `subject`, `preheader` and `document` are written
|
|
485
596
|
* in, and what every recipient without a ready version of their own gets.
|
|
@@ -503,21 +614,33 @@ var Template = z4.object({
|
|
|
503
614
|
* texts in `translations[tag].insertions`.
|
|
504
615
|
*/
|
|
505
616
|
insertions: Insertions,
|
|
617
|
+
/**
|
|
618
|
+
* What the sending app declared this template receives
|
|
619
|
+
* (`templates.declareInputs`); null until an app declares. Not part of a
|
|
620
|
+
* version: declaring never changes `version` or `updated_at`.
|
|
621
|
+
*/
|
|
622
|
+
inputs: TemplateInputs.nullable(),
|
|
623
|
+
/**
|
|
624
|
+
* The merge names real sends of this template carried, with a count and
|
|
625
|
+
* when first and last seen. Names only, never values. `{}` before the
|
|
626
|
+
* first send.
|
|
627
|
+
*/
|
|
628
|
+
inputs_seen: TemplateInputsSeen,
|
|
506
629
|
/** Increments on every saved change, of any language. */
|
|
507
|
-
version:
|
|
508
|
-
thumbnail_url:
|
|
630
|
+
version: z5.number().int().min(1),
|
|
631
|
+
thumbnail_url: z5.string().url().nullable(),
|
|
509
632
|
archived_at: Timestamp.nullable(),
|
|
510
633
|
created_at: Timestamp,
|
|
511
634
|
updated_at: Timestamp
|
|
512
635
|
});
|
|
513
|
-
var TemplateSummary = Template.omit({ document: true, translations: true, insertions: true }).extend({
|
|
514
|
-
languages:
|
|
636
|
+
var TemplateSummary = Template.omit({ document: true, translations: true, insertions: true, inputs: true, inputs_seen: true }).extend({
|
|
637
|
+
languages: z5.record(LocaleTag, TemplateLanguageStatus),
|
|
515
638
|
/** The names of the template's insertions, sorted alphabetically. */
|
|
516
|
-
insertion_names:
|
|
639
|
+
insertion_names: z5.array(z5.string())
|
|
517
640
|
});
|
|
518
|
-
var TemplateCreate =
|
|
519
|
-
name:
|
|
520
|
-
description:
|
|
641
|
+
var TemplateCreate = z5.object({
|
|
642
|
+
name: z5.string().min(1).max(200),
|
|
643
|
+
description: z5.string().max(2e3).optional(),
|
|
521
644
|
locale: LocaleTag.optional(),
|
|
522
645
|
subject: TemplateSubject.nullable().optional(),
|
|
523
646
|
preheader: TemplatePreheader.nullable().optional(),
|
|
@@ -525,15 +648,15 @@ var TemplateCreate = z4.object({
|
|
|
525
648
|
translations: TemplateTranslations.optional(),
|
|
526
649
|
insertions: Insertions.optional()
|
|
527
650
|
});
|
|
528
|
-
var TemplateUpdate =
|
|
529
|
-
base_version:
|
|
530
|
-
name:
|
|
531
|
-
description:
|
|
651
|
+
var TemplateUpdate = z5.object({
|
|
652
|
+
base_version: z5.number().int().min(1),
|
|
653
|
+
name: z5.string().min(1).max(200).optional(),
|
|
654
|
+
description: z5.string().max(2e3).nullable().optional(),
|
|
532
655
|
locale: LocaleTag.optional(),
|
|
533
656
|
subject: TemplateSubject.nullable().optional(),
|
|
534
657
|
preheader: TemplatePreheader.nullable().optional(),
|
|
535
658
|
document: TemplateDocument.optional(),
|
|
536
|
-
translations:
|
|
659
|
+
translations: z5.record(LocaleTag, TemplateTranslation.nullable()).optional(),
|
|
537
660
|
/**
|
|
538
661
|
* The main language's insertions, whole: what is left out is removed, from
|
|
539
662
|
* every language in the same save. A rename is a removal and an addition,
|
|
@@ -541,87 +664,87 @@ var TemplateUpdate = z4.object({
|
|
|
541
664
|
* save (it sends those languages in `translations`).
|
|
542
665
|
*/
|
|
543
666
|
insertions: Insertions.optional(),
|
|
544
|
-
archived:
|
|
667
|
+
archived: z5.boolean().optional()
|
|
545
668
|
}).refine(
|
|
546
669
|
(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,
|
|
547
670
|
"at least one field besides base_version"
|
|
548
671
|
);
|
|
549
|
-
var SavedSectionBody =
|
|
672
|
+
var SavedSectionBody = z5.record(z5.unknown());
|
|
550
673
|
var MAX_SAVED_SECTIONS = 50;
|
|
551
|
-
var SavedSection =
|
|
674
|
+
var SavedSection = z5.object({
|
|
552
675
|
id: Id,
|
|
553
|
-
name:
|
|
554
|
-
description:
|
|
676
|
+
name: z5.string().min(1).max(200),
|
|
677
|
+
description: z5.string().max(2e3).nullable(),
|
|
555
678
|
section: SavedSectionBody,
|
|
556
|
-
created_by:
|
|
679
|
+
created_by: z5.string().nullable(),
|
|
557
680
|
created_at: Timestamp,
|
|
558
681
|
updated_at: Timestamp
|
|
559
682
|
});
|
|
560
|
-
var SavedSectionCreate =
|
|
561
|
-
name:
|
|
562
|
-
description:
|
|
683
|
+
var SavedSectionCreate = z5.object({
|
|
684
|
+
name: z5.string().min(1).max(200),
|
|
685
|
+
description: z5.string().max(2e3).optional(),
|
|
563
686
|
section: SavedSectionBody
|
|
564
687
|
});
|
|
565
|
-
var SavedSectionUpdate =
|
|
566
|
-
name:
|
|
567
|
-
description:
|
|
688
|
+
var SavedSectionUpdate = z5.object({
|
|
689
|
+
name: z5.string().min(1).max(200).optional(),
|
|
690
|
+
description: z5.string().max(2e3).nullable().optional(),
|
|
568
691
|
section: SavedSectionBody.optional()
|
|
569
692
|
}).refine((v) => v.name !== void 0 || v.description !== void 0 || v.section !== void 0, "at least one field");
|
|
570
693
|
var TemplateListQuery = PageQuery.extend({
|
|
571
|
-
archived:
|
|
694
|
+
archived: z5.enum(["true", "false"]).optional()
|
|
572
695
|
});
|
|
573
|
-
var TemplateVersion =
|
|
696
|
+
var TemplateVersion = z5.object({
|
|
574
697
|
template_id: Id,
|
|
575
|
-
version:
|
|
698
|
+
version: z5.number().int().min(1),
|
|
576
699
|
locale: LocaleTag,
|
|
577
700
|
subject: TemplateSubject.nullable(),
|
|
578
701
|
preheader: TemplatePreheader.nullable(),
|
|
579
702
|
document: TemplateDocument,
|
|
580
703
|
translations: TemplateTranslations,
|
|
581
704
|
insertions: Insertions,
|
|
582
|
-
created_by:
|
|
705
|
+
created_by: z5.string().nullable(),
|
|
583
706
|
created_at: Timestamp
|
|
584
707
|
});
|
|
585
|
-
var TemplateVersionParams =
|
|
708
|
+
var TemplateVersionParams = z5.object({
|
|
586
709
|
id: Id,
|
|
587
|
-
version:
|
|
710
|
+
version: z5.coerce.number().int().min(1)
|
|
588
711
|
});
|
|
589
|
-
var CompileMessage =
|
|
590
|
-
message:
|
|
712
|
+
var CompileMessage = z5.object({
|
|
713
|
+
message: z5.string().min(1),
|
|
591
714
|
/** Where in the document or the MJML the message points, when known. */
|
|
592
|
-
path:
|
|
593
|
-
line:
|
|
715
|
+
path: z5.string().optional(),
|
|
716
|
+
line: z5.number().int().min(1).optional()
|
|
594
717
|
});
|
|
595
|
-
var CompileResult =
|
|
596
|
-
mjml:
|
|
597
|
-
html:
|
|
598
|
-
warnings:
|
|
599
|
-
errors:
|
|
718
|
+
var CompileResult = z5.object({
|
|
719
|
+
mjml: z5.string(),
|
|
720
|
+
html: z5.string(),
|
|
721
|
+
warnings: z5.array(CompileMessage),
|
|
722
|
+
errors: z5.array(CompileMessage)
|
|
600
723
|
});
|
|
601
|
-
var TemplateCompileRequest =
|
|
602
|
-
version:
|
|
724
|
+
var TemplateCompileRequest = z5.object({
|
|
725
|
+
version: z5.number().int().min(1).optional(),
|
|
603
726
|
locale: LocaleTag.optional()
|
|
604
727
|
});
|
|
605
|
-
var CompileRequest =
|
|
728
|
+
var CompileRequest = z5.object({
|
|
606
729
|
document: TemplateDocument
|
|
607
730
|
});
|
|
608
731
|
var ASSET_CONTENT_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
609
|
-
var AssetContentType =
|
|
732
|
+
var AssetContentType = z5.enum(ASSET_CONTENT_TYPES);
|
|
610
733
|
var MAX_ASSET_BYTES = 10 * 1024 * 1024;
|
|
611
|
-
var Asset =
|
|
734
|
+
var Asset = z5.object({
|
|
612
735
|
id: Id,
|
|
613
|
-
url:
|
|
736
|
+
url: z5.string().url(),
|
|
614
737
|
content_type: AssetContentType,
|
|
615
|
-
size_bytes:
|
|
616
|
-
width:
|
|
617
|
-
height:
|
|
618
|
-
filename:
|
|
738
|
+
size_bytes: z5.number().int().min(1).max(MAX_ASSET_BYTES),
|
|
739
|
+
width: z5.number().int().min(1).nullable(),
|
|
740
|
+
height: z5.number().int().min(1).nullable(),
|
|
741
|
+
filename: z5.string().min(1).max(255),
|
|
619
742
|
created_at: Timestamp
|
|
620
743
|
});
|
|
621
744
|
var ASSET_UPLOAD_FIELD = "file";
|
|
622
745
|
var MAX_IMPORT_URL_LENGTH = 2048;
|
|
623
|
-
var AssetImport =
|
|
624
|
-
url:
|
|
746
|
+
var AssetImport = z5.object({
|
|
747
|
+
url: z5.string().max(MAX_IMPORT_URL_LENGTH).url().refine((u) => {
|
|
625
748
|
try {
|
|
626
749
|
const url = new URL(u);
|
|
627
750
|
if (url.protocol === "https:") return true;
|
|
@@ -631,7 +754,7 @@ var AssetImport = z4.object({
|
|
|
631
754
|
}
|
|
632
755
|
}, "must be https (http only for localhost)"),
|
|
633
756
|
/** The stored file name; derived from the address when omitted. */
|
|
634
|
-
filename:
|
|
757
|
+
filename: z5.string().min(1).max(255).optional()
|
|
635
758
|
});
|
|
636
759
|
var MAX_MJML_IMPORT_BYTES = 512 * 1024;
|
|
637
760
|
var MAX_MJML_DEPTH = 32;
|
|
@@ -646,56 +769,56 @@ var MJML_IMPORT_REFUSALS = [
|
|
|
646
769
|
"too_complex",
|
|
647
770
|
"invalid_document"
|
|
648
771
|
];
|
|
649
|
-
var ImportWarning =
|
|
650
|
-
severity:
|
|
772
|
+
var ImportWarning = z5.object({
|
|
773
|
+
severity: z5.enum(["info", "warning"]),
|
|
651
774
|
/** Stable kind, e.g. `kept_as_html`, `unknown_component`, `remote_image_not_imported`. */
|
|
652
|
-
code:
|
|
775
|
+
code: z5.string().min(1).max(64),
|
|
653
776
|
/** Where in the source, e.g. `mj-body > mj-section[2] > mj-column[1] > mj-social[1]`. */
|
|
654
|
-
path:
|
|
655
|
-
line:
|
|
656
|
-
message:
|
|
657
|
-
fragment:
|
|
777
|
+
path: z5.string(),
|
|
778
|
+
line: z5.number().int().min(1).optional(),
|
|
779
|
+
message: z5.string().min(1),
|
|
780
|
+
fragment: z5.string().optional()
|
|
658
781
|
});
|
|
659
|
-
var RemoteImage =
|
|
660
|
-
url:
|
|
782
|
+
var RemoteImage = z5.object({
|
|
783
|
+
url: z5.string().min(1),
|
|
661
784
|
/** The element and attribute it is loaded from, e.g. `<img src>`. */
|
|
662
|
-
where:
|
|
785
|
+
where: z5.string()
|
|
663
786
|
});
|
|
664
|
-
var ImportedAsset =
|
|
665
|
-
source_url:
|
|
787
|
+
var ImportedAsset = z5.object({
|
|
788
|
+
source_url: z5.string().min(1),
|
|
666
789
|
asset_id: Id,
|
|
667
|
-
url:
|
|
790
|
+
url: z5.string().url()
|
|
668
791
|
});
|
|
669
|
-
var mjmlSource =
|
|
670
|
-
var TemplateImportPreviewRequest =
|
|
671
|
-
var TemplateImportPreview =
|
|
792
|
+
var mjmlSource = z5.string().min(1, "paste or upload the MJML");
|
|
793
|
+
var TemplateImportPreviewRequest = z5.object({ mjml: mjmlSource });
|
|
794
|
+
var TemplateImportPreview = z5.object({
|
|
672
795
|
document: TemplateDocument,
|
|
673
|
-
warnings:
|
|
796
|
+
warnings: z5.array(ImportWarning),
|
|
674
797
|
/** The document compiled under the workspace's asset policy, exactly as a send would. */
|
|
675
798
|
compiled: CompileResult,
|
|
676
799
|
/** Images loaded from outside the service; under `service_only` each is also a compile error. */
|
|
677
|
-
remote_images:
|
|
678
|
-
asset_policy:
|
|
800
|
+
remote_images: z5.array(RemoteImage),
|
|
801
|
+
asset_policy: z5.enum(["any", "service_only"])
|
|
679
802
|
});
|
|
680
|
-
var TemplateImport =
|
|
681
|
-
name:
|
|
682
|
-
description:
|
|
803
|
+
var TemplateImport = z5.object({
|
|
804
|
+
name: z5.string().min(1).max(200),
|
|
805
|
+
description: z5.string().max(2e3).optional(),
|
|
683
806
|
mjml: mjmlSource,
|
|
684
|
-
import_remote_assets:
|
|
807
|
+
import_remote_assets: z5.boolean().optional()
|
|
685
808
|
});
|
|
686
|
-
var TemplateImportResult =
|
|
809
|
+
var TemplateImportResult = z5.object({
|
|
687
810
|
template: Template,
|
|
688
|
-
warnings:
|
|
689
|
-
imported_assets:
|
|
811
|
+
warnings: z5.array(ImportWarning),
|
|
812
|
+
imported_assets: z5.array(ImportedAsset)
|
|
690
813
|
});
|
|
691
814
|
var EXPORT_FORMATS = ["mjml", "html"];
|
|
692
|
-
var ExportFormat =
|
|
693
|
-
var TemplateExportQuery =
|
|
815
|
+
var ExportFormat = z5.enum(EXPORT_FORMATS);
|
|
816
|
+
var TemplateExportQuery = z5.object({
|
|
694
817
|
format: ExportFormat,
|
|
695
|
-
version:
|
|
818
|
+
version: z5.coerce.number().int().min(1).optional(),
|
|
696
819
|
locale: LocaleTag.optional()
|
|
697
820
|
});
|
|
698
|
-
var MailingExportQuery =
|
|
821
|
+
var MailingExportQuery = z5.object({ format: ExportFormat });
|
|
699
822
|
var EXPORT_CONTENT_TYPES = {
|
|
700
823
|
mjml: "text/plain; charset=utf-8",
|
|
701
824
|
html: "text/html; charset=utf-8"
|
|
@@ -714,7 +837,7 @@ function formatExportWarningsHeader(messages) {
|
|
|
714
837
|
function parseExportWarningsHeader(value) {
|
|
715
838
|
if (!value) return [];
|
|
716
839
|
try {
|
|
717
|
-
const parsed =
|
|
840
|
+
const parsed = z5.array(CompileMessage).safeParse(JSON.parse(decodeURIComponent(value)));
|
|
718
841
|
return parsed.success ? parsed.data : [];
|
|
719
842
|
} catch {
|
|
720
843
|
return [];
|
|
@@ -726,32 +849,32 @@ function exportFilename(name, format) {
|
|
|
726
849
|
}
|
|
727
850
|
|
|
728
851
|
// src/platform.ts
|
|
729
|
-
import { z as
|
|
852
|
+
import { z as z8 } from "zod";
|
|
730
853
|
|
|
731
854
|
// src/mailings.ts
|
|
732
|
-
import { z as
|
|
855
|
+
import { z as z7 } from "zod";
|
|
733
856
|
|
|
734
857
|
// src/ab-test.ts
|
|
735
|
-
import { z as
|
|
736
|
-
var AbVariant =
|
|
737
|
-
key:
|
|
738
|
-
subject:
|
|
858
|
+
import { z as z6 } from "zod";
|
|
859
|
+
var AbVariant = z6.object({
|
|
860
|
+
key: z6.string().regex(/^[a-z]$/, "a single lowercase letter"),
|
|
861
|
+
subject: z6.string().min(1).max(998).optional(),
|
|
739
862
|
/** Replaces the mailing's document for this variant. */
|
|
740
|
-
document:
|
|
863
|
+
document: z6.record(z6.unknown()).optional(),
|
|
741
864
|
/**
|
|
742
865
|
* Keeps the document this variant key already has in the mailing's current
|
|
743
866
|
* test, so a test can be changed without sending every document again. The
|
|
744
867
|
* service refuses it (`validation_failed`) when that variant has none.
|
|
745
868
|
*/
|
|
746
|
-
keep_document:
|
|
869
|
+
keep_document: z6.literal(true).optional()
|
|
747
870
|
});
|
|
748
871
|
var AB_WINNER_METRICS = ["opens", "clicks", "manual"];
|
|
749
|
-
var AbTestConfig =
|
|
750
|
-
variants:
|
|
872
|
+
var AbTestConfig = z6.object({
|
|
873
|
+
variants: z6.array(AbVariant).min(2).max(5),
|
|
751
874
|
/** Share of recipients in the test group, split evenly across variants; the rest get the winner. */
|
|
752
|
-
test_fraction:
|
|
753
|
-
winner_metric:
|
|
754
|
-
decide_after_minutes:
|
|
875
|
+
test_fraction: z6.number().gt(0).lte(1),
|
|
876
|
+
winner_metric: z6.enum(AB_WINNER_METRICS),
|
|
877
|
+
decide_after_minutes: z6.number().int().min(15).max(10080).optional()
|
|
755
878
|
}).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), {
|
|
756
879
|
message: "every variant changes the subject, the document or both",
|
|
757
880
|
path: ["variants"]
|
|
@@ -763,19 +886,19 @@ var AbTestConfig = z5.object({
|
|
|
763
886
|
path: ["decide_after_minutes"]
|
|
764
887
|
});
|
|
765
888
|
var AB_TEST_STATUSES = ["pending", "testing", "awaiting_pick", "decided"];
|
|
766
|
-
var AbTestState =
|
|
767
|
-
variants:
|
|
768
|
-
test_fraction:
|
|
769
|
-
winner_metric:
|
|
770
|
-
decide_after_minutes:
|
|
771
|
-
status:
|
|
889
|
+
var AbTestState = z6.object({
|
|
890
|
+
variants: z6.array(z6.object({ key: z6.string(), subject: z6.string().nullable(), has_document: z6.boolean() })),
|
|
891
|
+
test_fraction: z6.number(),
|
|
892
|
+
winner_metric: z6.enum(AB_WINNER_METRICS),
|
|
893
|
+
decide_after_minutes: z6.number().int().nullable(),
|
|
894
|
+
status: z6.enum(AB_TEST_STATUSES),
|
|
772
895
|
/** When the metric decides; null for manual or before the start. */
|
|
773
896
|
decide_at: Timestamp.nullable(),
|
|
774
|
-
winner:
|
|
775
|
-
decided_by:
|
|
897
|
+
winner: z6.string().nullable(),
|
|
898
|
+
decided_by: z6.enum(["metric", "manual"]).nullable(),
|
|
776
899
|
decided_at: Timestamp.nullable()
|
|
777
900
|
});
|
|
778
|
-
var AbWinnerRequest =
|
|
901
|
+
var AbWinnerRequest = z6.object({ variant: z6.string().regex(/^[a-z]$/) });
|
|
779
902
|
function testGroupSize(total, fraction, variants) {
|
|
780
903
|
return Math.min(total, Math.max(variants, Math.ceil(total * fraction)));
|
|
781
904
|
}
|
|
@@ -790,9 +913,9 @@ var MAILING_STATUSES = [
|
|
|
790
913
|
"partially_failed",
|
|
791
914
|
"cancelled"
|
|
792
915
|
];
|
|
793
|
-
var MailingStatus =
|
|
916
|
+
var MailingStatus = z7.enum(MAILING_STATUSES);
|
|
794
917
|
var MAILING_ACTIONS = ["send", "schedule", "unschedule", "pause", "resume", "cancel", "retry-failed"];
|
|
795
|
-
var MailingAction =
|
|
918
|
+
var MailingAction = z7.enum(MAILING_ACTIONS);
|
|
796
919
|
var MAILING_TRANSITIONS = {
|
|
797
920
|
draft: ["send", "schedule", "cancel"],
|
|
798
921
|
scheduled: ["send", "schedule", "unschedule", "cancel"],
|
|
@@ -807,23 +930,23 @@ function canTransition(status, action) {
|
|
|
807
930
|
}
|
|
808
931
|
var EDITABLE_MAILING_STATUSES = ["draft", "scheduled"];
|
|
809
932
|
var TERMINAL_MAILING_STATUSES = ["sent", "cancelled"];
|
|
810
|
-
var MailingMetadata =
|
|
811
|
-
var MailingCounts =
|
|
812
|
-
total:
|
|
813
|
-
queued:
|
|
814
|
-
sending:
|
|
815
|
-
sent:
|
|
816
|
-
failed:
|
|
817
|
-
skipped:
|
|
933
|
+
var MailingMetadata = z7.record(z7.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");
|
|
934
|
+
var MailingCounts = z7.object({
|
|
935
|
+
total: z7.number().int().min(0),
|
|
936
|
+
queued: z7.number().int().min(0),
|
|
937
|
+
sending: z7.number().int().min(0),
|
|
938
|
+
sent: z7.number().int().min(0),
|
|
939
|
+
failed: z7.number().int().min(0),
|
|
940
|
+
skipped: z7.number().int().min(0)
|
|
818
941
|
});
|
|
819
942
|
var MAILING_KINDS = ["broadcast", "automation", "notification"];
|
|
820
|
-
var MailingKind =
|
|
821
|
-
var Mailing =
|
|
943
|
+
var MailingKind = z7.enum(MAILING_KINDS);
|
|
944
|
+
var Mailing = z7.object({
|
|
822
945
|
id: Id,
|
|
823
946
|
kind: MailingKind,
|
|
824
|
-
name:
|
|
825
|
-
subject:
|
|
826
|
-
preheader:
|
|
947
|
+
name: z7.string().max(200).nullable(),
|
|
948
|
+
subject: z7.string().min(1).max(998),
|
|
949
|
+
preheader: z7.string().max(500).nullable(),
|
|
827
950
|
/**
|
|
828
951
|
* The template the document was taken from, if any. The snapshot is what is
|
|
829
952
|
* sent in the main language; the template's other language versions are
|
|
@@ -868,15 +991,15 @@ var Mailing = z6.object({
|
|
|
868
991
|
* recipients refused as dead addresses in one run (see the provider's
|
|
869
992
|
* `rejections.anomaly`). Resuming clears it and starts a new run.
|
|
870
993
|
*/
|
|
871
|
-
pause_reason:
|
|
994
|
+
pause_reason: z7.string().nullable().default(null),
|
|
872
995
|
created_at: Timestamp,
|
|
873
996
|
updated_at: Timestamp
|
|
874
997
|
});
|
|
875
998
|
var MailingSummary = Mailing.omit({ document: true });
|
|
876
999
|
var MailingContent = {
|
|
877
|
-
name:
|
|
878
|
-
subject:
|
|
879
|
-
preheader:
|
|
1000
|
+
name: z7.string().max(200).optional(),
|
|
1001
|
+
subject: z7.string().min(1).max(998),
|
|
1002
|
+
preheader: z7.string().max(500).optional(),
|
|
880
1003
|
/** See `Mailing.send_locale`. */
|
|
881
1004
|
send_locale: LocaleTag.nullable().optional(),
|
|
882
1005
|
/**
|
|
@@ -889,7 +1012,7 @@ var MailingContent = {
|
|
|
889
1012
|
provider_id: Id,
|
|
890
1013
|
metadata: MailingMetadata.optional()
|
|
891
1014
|
};
|
|
892
|
-
var MailingCreate =
|
|
1015
|
+
var MailingCreate = z7.object({
|
|
893
1016
|
...MailingContent,
|
|
894
1017
|
subject: MailingContent.subject.optional(),
|
|
895
1018
|
document: TemplateDocument.optional(),
|
|
@@ -901,10 +1024,10 @@ var MailingCreate = z6.object({
|
|
|
901
1024
|
message: "a mailing made from a document needs a subject",
|
|
902
1025
|
path: ["subject"]
|
|
903
1026
|
});
|
|
904
|
-
var MailingUpdate =
|
|
905
|
-
name:
|
|
1027
|
+
var MailingUpdate = z7.object({
|
|
1028
|
+
name: z7.string().max(200).nullable().optional(),
|
|
906
1029
|
subject: MailingContent.subject.optional(),
|
|
907
|
-
preheader:
|
|
1030
|
+
preheader: z7.string().max(500).nullable().optional(),
|
|
908
1031
|
/** Null turns a draft into a letter, which it can only be with at most one recipient. */
|
|
909
1032
|
topic: Slug.nullable().optional(),
|
|
910
1033
|
send_locale: LocaleTag.nullable().optional(),
|
|
@@ -918,30 +1041,30 @@ var MailingListQuery = PageQuery.extend({
|
|
|
918
1041
|
kind: MailingKind.optional(),
|
|
919
1042
|
automation_id: Id.optional()
|
|
920
1043
|
});
|
|
921
|
-
var MailingSendRequest =
|
|
922
|
-
var MailingRetryFailedRequest =
|
|
1044
|
+
var MailingSendRequest = z7.object({}).strict();
|
|
1045
|
+
var MailingRetryFailedRequest = z7.object({
|
|
923
1046
|
/**
|
|
924
1047
|
* Also requeue recipients whose outcome is unknown after a crash. They may
|
|
925
1048
|
* already have received the message, so this is a human's decision, never a
|
|
926
1049
|
* default.
|
|
927
1050
|
*/
|
|
928
|
-
include_outcome_unknown:
|
|
1051
|
+
include_outcome_unknown: z7.boolean().optional()
|
|
929
1052
|
}).strict();
|
|
930
|
-
var MailingActionRequest =
|
|
931
|
-
var MailingTestRequest =
|
|
1053
|
+
var MailingActionRequest = z7.object({}).strict();
|
|
1054
|
+
var MailingTestRequest = z7.object({
|
|
932
1055
|
to: Email,
|
|
933
1056
|
merge: Properties.optional(),
|
|
934
1057
|
locale: LocaleTag.optional()
|
|
935
1058
|
});
|
|
936
|
-
var MailingTestResult =
|
|
1059
|
+
var MailingTestResult = z7.object({
|
|
937
1060
|
message_id: Id,
|
|
938
|
-
provider_message_id:
|
|
1061
|
+
provider_message_id: z7.string().nullable()
|
|
939
1062
|
});
|
|
940
1063
|
var RECIPIENT_STATUSES = ["queued", "sending", "sent", "failed", "skipped"];
|
|
941
|
-
var RecipientStatus =
|
|
1064
|
+
var RecipientStatus = z7.enum(RECIPIENT_STATUSES);
|
|
942
1065
|
var SKIP_REASONS = ["suppressed", "not_subscribed", "contact_erased", "cancelled", "outcome_unknown"];
|
|
943
|
-
var SkipReason =
|
|
944
|
-
var Recipient =
|
|
1066
|
+
var SkipReason = z7.enum(SKIP_REASONS);
|
|
1067
|
+
var Recipient = z7.object({
|
|
945
1068
|
id: Id,
|
|
946
1069
|
mailing_id: Id,
|
|
947
1070
|
contact_id: Id.nullable(),
|
|
@@ -949,55 +1072,55 @@ var Recipient = z6.object({
|
|
|
949
1072
|
merge: Properties,
|
|
950
1073
|
status: RecipientStatus,
|
|
951
1074
|
skip_reason: SkipReason.nullable(),
|
|
952
|
-
attempts:
|
|
1075
|
+
attempts: z7.number().int().min(0),
|
|
953
1076
|
message_id: Id.nullable(),
|
|
954
|
-
last_error:
|
|
1077
|
+
last_error: z7.string().nullable(),
|
|
955
1078
|
created_at: Timestamp,
|
|
956
1079
|
updated_at: Timestamp
|
|
957
1080
|
});
|
|
958
1081
|
var MAX_RECIPIENTS_PER_BATCH = 1e3;
|
|
959
1082
|
var MAX_LETTER_RECIPIENTS = 1;
|
|
960
1083
|
var LETTER_RECIPIENT_REFUSAL = "letter_has_one_recipient";
|
|
961
|
-
var RecipientInput =
|
|
1084
|
+
var RecipientInput = z7.object({
|
|
962
1085
|
contact_id: Id.optional(),
|
|
963
|
-
external_id:
|
|
1086
|
+
external_id: z7.string().min(1).max(255).optional(),
|
|
964
1087
|
email: Email.optional(),
|
|
965
1088
|
merge: Properties.optional()
|
|
966
1089
|
}).refine((v) => v.contact_id !== void 0 || v.external_id !== void 0 || v.email !== void 0, {
|
|
967
1090
|
message: "contact_id, external_id or email is required",
|
|
968
1091
|
path: ["email"]
|
|
969
1092
|
});
|
|
970
|
-
var RecipientBatch =
|
|
971
|
-
recipients:
|
|
1093
|
+
var RecipientBatch = z7.object({
|
|
1094
|
+
recipients: z7.array(RecipientInput).min(1).max(MAX_RECIPIENTS_PER_BATCH)
|
|
972
1095
|
});
|
|
973
1096
|
var RECIPIENT_REJECTIONS = ["unknown_contact", "duplicate_in_batch"];
|
|
974
|
-
var RecipientBatchResult =
|
|
1097
|
+
var RecipientBatchResult = z7.object({
|
|
975
1098
|
/** Newly queued. */
|
|
976
|
-
added:
|
|
1099
|
+
added: z7.number().int().min(0),
|
|
977
1100
|
/** Already on the mailing from an earlier batch: left unchanged. */
|
|
978
|
-
already_present:
|
|
1101
|
+
already_present: z7.number().int().min(0),
|
|
979
1102
|
/** Items not added, by their index in the request. */
|
|
980
|
-
rejected:
|
|
1103
|
+
rejected: z7.array(z7.object({ index: z7.number().int().min(0), reason: z7.enum(RECIPIENT_REJECTIONS) }))
|
|
981
1104
|
});
|
|
982
1105
|
var RecipientListQuery = PageQuery.extend({
|
|
983
1106
|
status: RecipientStatus.optional(),
|
|
984
1107
|
skip_reason: SkipReason.optional()
|
|
985
1108
|
});
|
|
986
1109
|
var MESSAGE_OUTCOMES = ["sent", "failed"];
|
|
987
|
-
var MessageOutcome =
|
|
988
|
-
var MessageSummary =
|
|
1110
|
+
var MessageOutcome = z7.enum(MESSAGE_OUTCOMES);
|
|
1111
|
+
var MessageSummary = z7.object({
|
|
989
1112
|
id: Id,
|
|
990
1113
|
mailing_id: Id.nullable(),
|
|
991
1114
|
recipient_id: Id.nullable(),
|
|
992
1115
|
contact_id: Id.nullable(),
|
|
993
1116
|
to: Email,
|
|
994
|
-
subject:
|
|
1117
|
+
subject: z7.string(),
|
|
995
1118
|
provider_id: Id,
|
|
996
|
-
provider_message_id:
|
|
1119
|
+
provider_message_id: z7.string().nullable(),
|
|
997
1120
|
outcome: MessageOutcome,
|
|
998
|
-
error:
|
|
999
|
-
is_test:
|
|
1000
|
-
recipient_count:
|
|
1121
|
+
error: z7.string().nullable(),
|
|
1122
|
+
is_test: z7.boolean(),
|
|
1123
|
+
recipient_count: z7.number().int().min(1),
|
|
1001
1124
|
/** The language version the message was sent in; null for messages archived before languages existed. */
|
|
1002
1125
|
locale: LocaleTag.nullable(),
|
|
1003
1126
|
/**
|
|
@@ -1009,22 +1132,22 @@ var MessageSummary = z6.object({
|
|
|
1009
1132
|
insertions: InsertionChoices.nullable(),
|
|
1010
1133
|
created_at: Timestamp
|
|
1011
1134
|
});
|
|
1012
|
-
var Message = MessageSummary.extend({ html:
|
|
1013
|
-
var MailingLanguageStatus =
|
|
1014
|
-
var MailingLanguage =
|
|
1135
|
+
var Message = MessageSummary.extend({ html: z7.string() });
|
|
1136
|
+
var MailingLanguageStatus = z7.enum(["main", "ready", "draft", "not_a_workspace_language"]);
|
|
1137
|
+
var MailingLanguage = z7.object({
|
|
1015
1138
|
locale: LocaleTag,
|
|
1016
1139
|
status: MailingLanguageStatus,
|
|
1017
1140
|
/** Queued recipients who will get this version. */
|
|
1018
|
-
planned:
|
|
1141
|
+
planned: z7.number().int().min(0),
|
|
1019
1142
|
/** Messages already sent in this language (tests excluded). */
|
|
1020
|
-
sent:
|
|
1143
|
+
sent: z7.number().int().min(0)
|
|
1021
1144
|
});
|
|
1022
|
-
var MailingLanguageReport =
|
|
1145
|
+
var MailingLanguageReport = z7.object({
|
|
1023
1146
|
main_locale: LocaleTag,
|
|
1024
1147
|
send_locale: LocaleTag.nullable(),
|
|
1025
|
-
languages:
|
|
1026
|
-
fallback:
|
|
1027
|
-
no_language:
|
|
1148
|
+
languages: z7.array(MailingLanguage),
|
|
1149
|
+
fallback: z7.array(z7.object({ wanted: z7.string(), planned: z7.number().int().min(1) })),
|
|
1150
|
+
no_language: z7.number().int().min(0),
|
|
1028
1151
|
/**
|
|
1029
1152
|
* Values that no choice of an insertion takes, so the "otherwise" text goes
|
|
1030
1153
|
* out (docs/plans/2026-09-25-translated-insertions.md, decision 3): per
|
|
@@ -1033,12 +1156,12 @@ var MailingLanguageReport = z6.object({
|
|
|
1033
1156
|
* template's keys never produces one; a non-empty list means the client and
|
|
1034
1157
|
* the template have drifted apart.
|
|
1035
1158
|
*/
|
|
1036
|
-
insertion_misses:
|
|
1037
|
-
|
|
1038
|
-
insertion:
|
|
1039
|
-
value:
|
|
1040
|
-
planned:
|
|
1041
|
-
sent:
|
|
1159
|
+
insertion_misses: z7.array(
|
|
1160
|
+
z7.object({
|
|
1161
|
+
insertion: z7.string(),
|
|
1162
|
+
value: z7.string(),
|
|
1163
|
+
planned: z7.number().int().min(0),
|
|
1164
|
+
sent: z7.number().int().min(0)
|
|
1042
1165
|
})
|
|
1043
1166
|
)
|
|
1044
1167
|
});
|
|
@@ -1048,26 +1171,26 @@ var MessageListQuery = PageQuery.extend({
|
|
|
1048
1171
|
});
|
|
1049
1172
|
|
|
1050
1173
|
// src/platform.ts
|
|
1051
|
-
var Tag =
|
|
1174
|
+
var Tag = z8.object({
|
|
1052
1175
|
id: Id,
|
|
1053
1176
|
slug: Slug,
|
|
1054
|
-
name:
|
|
1055
|
-
contact_count:
|
|
1177
|
+
name: z8.string().min(1).max(120),
|
|
1178
|
+
contact_count: z8.number().int().min(0),
|
|
1056
1179
|
created_at: Timestamp
|
|
1057
1180
|
});
|
|
1058
|
-
var TagCreate =
|
|
1059
|
-
var TagAssignment =
|
|
1060
|
-
contact_ids:
|
|
1181
|
+
var TagCreate = z8.object({ slug: Slug, name: z8.string().min(1).max(120) });
|
|
1182
|
+
var TagAssignment = z8.object({
|
|
1183
|
+
contact_ids: z8.array(Id).min(1).max(1e3)
|
|
1061
1184
|
});
|
|
1062
1185
|
var CONTACT_PROPERTY_TYPES = ["string", "number", "boolean", "date"];
|
|
1063
|
-
var ContactPropertyType =
|
|
1064
|
-
var ContactPropertyKey =
|
|
1065
|
-
var ContactPropertyDefinition =
|
|
1186
|
+
var ContactPropertyType = z8.enum(CONTACT_PROPERTY_TYPES);
|
|
1187
|
+
var ContactPropertyKey = z8.string().regex(/^[A-Za-z0-9_.-]{1,64}$/, "letters, digits, dot, underscore and hyphen, 1 to 64");
|
|
1188
|
+
var ContactPropertyDefinition = z8.object({
|
|
1066
1189
|
key: ContactPropertyKey,
|
|
1067
|
-
label:
|
|
1190
|
+
label: z8.string().min(1).max(120),
|
|
1068
1191
|
type: ContactPropertyType
|
|
1069
1192
|
});
|
|
1070
|
-
var ContactPropertyParams =
|
|
1193
|
+
var ContactPropertyParams = z8.object({ key: ContactPropertyKey });
|
|
1071
1194
|
var FILTER_OPERATORS = [
|
|
1072
1195
|
"eq",
|
|
1073
1196
|
"neq",
|
|
@@ -1083,8 +1206,8 @@ var FILTER_OPERATORS = [
|
|
|
1083
1206
|
"in",
|
|
1084
1207
|
"not_in"
|
|
1085
1208
|
];
|
|
1086
|
-
var FilterOperator =
|
|
1087
|
-
var FilterField =
|
|
1209
|
+
var FilterOperator = z8.enum(FILTER_OPERATORS);
|
|
1210
|
+
var FilterField = z8.string().regex(
|
|
1088
1211
|
/^(email|first_name|last_name|locale|created_at|tag|topic|engagement:(opened|clicked)|property:[A-Za-z0-9_.-]{1,64})$/,
|
|
1089
1212
|
"unknown filter field"
|
|
1090
1213
|
);
|
|
@@ -1100,18 +1223,18 @@ var FILTER_FIELD_OPERATORS = {
|
|
|
1100
1223
|
property: FILTER_OPERATORS
|
|
1101
1224
|
};
|
|
1102
1225
|
var MAX_FILTER_DEPTH = 5;
|
|
1103
|
-
var FilterCondition =
|
|
1226
|
+
var FilterCondition = z8.object({ field: FilterField, op: FilterOperator, value: JsonValue.optional() }).strict().refine((c) => (c.op === "exists" || c.op === "not_exists") === (c.value === void 0), {
|
|
1104
1227
|
message: "exists and not_exists take no value; every other operator needs one",
|
|
1105
1228
|
path: ["value"]
|
|
1106
1229
|
}).refine((c) => c.op !== "in" && c.op !== "not_in" || Array.isArray(c.value), {
|
|
1107
1230
|
message: "in and not_in take an array",
|
|
1108
1231
|
path: ["value"]
|
|
1109
1232
|
});
|
|
1110
|
-
var SegmentFilter =
|
|
1111
|
-
() =>
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1233
|
+
var SegmentFilter = z8.lazy(
|
|
1234
|
+
() => z8.union([
|
|
1235
|
+
z8.object({ and: z8.array(SegmentFilter).min(1).max(50) }).strict(),
|
|
1236
|
+
z8.object({ or: z8.array(SegmentFilter).min(1).max(50) }).strict(),
|
|
1237
|
+
z8.object({ not: SegmentFilter }).strict(),
|
|
1115
1238
|
FilterCondition
|
|
1116
1239
|
])
|
|
1117
1240
|
);
|
|
@@ -1199,42 +1322,42 @@ function untypedValueProblem(c, every) {
|
|
|
1199
1322
|
if (orderOp) return every(isFilterNumber, "numbers (define the property to compare dates)");
|
|
1200
1323
|
return every((v) => isScalar(v) || v === null, "strings, numbers, booleans or null");
|
|
1201
1324
|
}
|
|
1202
|
-
var Segment =
|
|
1325
|
+
var Segment = z8.object({
|
|
1203
1326
|
id: Id,
|
|
1204
|
-
name:
|
|
1327
|
+
name: z8.string().min(1).max(120),
|
|
1205
1328
|
filter: SegmentFilter,
|
|
1206
1329
|
/** Computed on read. */
|
|
1207
|
-
contact_count:
|
|
1330
|
+
contact_count: z8.number().int().min(0),
|
|
1208
1331
|
created_at: Timestamp,
|
|
1209
1332
|
updated_at: Timestamp
|
|
1210
1333
|
});
|
|
1211
|
-
var SegmentCreate =
|
|
1212
|
-
var SegmentPreviewRequest =
|
|
1213
|
-
var SegmentPreview =
|
|
1214
|
-
contact_count:
|
|
1334
|
+
var SegmentCreate = z8.object({ name: z8.string().min(1).max(120), filter: BoundedSegmentFilter });
|
|
1335
|
+
var SegmentPreviewRequest = z8.object({ filter: BoundedSegmentFilter });
|
|
1336
|
+
var SegmentPreview = z8.object({
|
|
1337
|
+
contact_count: z8.number().int().min(0),
|
|
1215
1338
|
/** Up to five matching contacts, for a human to sanity-check the filter. */
|
|
1216
|
-
sample:
|
|
1339
|
+
sample: z8.array(z8.object({ id: Id, email: Email })).max(5)
|
|
1217
1340
|
});
|
|
1218
|
-
var MailingAudienceFromSegment =
|
|
1341
|
+
var MailingAudienceFromSegment = z8.object({ segment_id: Id });
|
|
1219
1342
|
var SIGNUP_FORM_FIELDS = ["first_name", "last_name"];
|
|
1220
1343
|
var HOSTED_PAGE_LOCALES = ["en", "de", "it", "fr", "es"];
|
|
1221
|
-
var SignupFormTranslation =
|
|
1222
|
-
title:
|
|
1223
|
-
consent_text:
|
|
1344
|
+
var SignupFormTranslation = z8.object({
|
|
1345
|
+
title: z8.string().min(1).max(120),
|
|
1346
|
+
consent_text: z8.string().min(1).max(2e3)
|
|
1224
1347
|
});
|
|
1225
|
-
var SignupForm =
|
|
1348
|
+
var SignupForm = z8.object({
|
|
1226
1349
|
id: Id,
|
|
1227
|
-
name:
|
|
1350
|
+
name: z8.string().min(1).max(120),
|
|
1228
1351
|
/** The heading of the hosted page. */
|
|
1229
|
-
title:
|
|
1352
|
+
title: z8.string().min(1).max(120),
|
|
1230
1353
|
/** What the person agrees to, shown above the button and recorded with the confirmation. */
|
|
1231
|
-
consent_text:
|
|
1232
|
-
translations:
|
|
1233
|
-
topics:
|
|
1234
|
-
tags:
|
|
1354
|
+
consent_text: z8.string().min(1).max(2e3),
|
|
1355
|
+
translations: z8.record(SignupFormTranslation),
|
|
1356
|
+
topics: z8.array(Slug).min(1),
|
|
1357
|
+
tags: z8.array(Slug),
|
|
1235
1358
|
/** Fields shown besides email. */
|
|
1236
|
-
fields:
|
|
1237
|
-
double_opt_in:
|
|
1359
|
+
fields: z8.array(z8.enum(SIGNUP_FORM_FIELDS)),
|
|
1360
|
+
double_opt_in: z8.literal(true),
|
|
1238
1361
|
/** The provider the confirmation mail is sent through. */
|
|
1239
1362
|
provider_id: Id,
|
|
1240
1363
|
/**
|
|
@@ -1243,11 +1366,11 @@ var SignupForm = z7.object({
|
|
|
1243
1366
|
*/
|
|
1244
1367
|
confirmation_template_id: Id.nullable(),
|
|
1245
1368
|
/** Where the person lands after confirming. Null shows the hosted confirmation page. */
|
|
1246
|
-
redirect_url:
|
|
1369
|
+
redirect_url: z8.string().url().nullable(),
|
|
1247
1370
|
/** Origins allowed to embed the form and call the submission route from a browser. */
|
|
1248
|
-
allowed_origins:
|
|
1371
|
+
allowed_origins: z8.array(z8.string().url()).max(20),
|
|
1249
1372
|
/** Bumped on every change; recorded with each confirmation. */
|
|
1250
|
-
version:
|
|
1373
|
+
version: z8.number().int().min(1),
|
|
1251
1374
|
created_at: Timestamp,
|
|
1252
1375
|
updated_at: Timestamp
|
|
1253
1376
|
});
|
|
@@ -1262,56 +1385,56 @@ var SignupFormCreate = SignupForm.omit({
|
|
|
1262
1385
|
tags: SignupForm.shape.tags.optional(),
|
|
1263
1386
|
fields: SignupForm.shape.fields.optional(),
|
|
1264
1387
|
confirmation_template_id: Id.nullable().optional(),
|
|
1265
|
-
redirect_url:
|
|
1388
|
+
redirect_url: z8.string().url().nullable().optional(),
|
|
1266
1389
|
allowed_origins: SignupForm.shape.allowed_origins.optional()
|
|
1267
1390
|
});
|
|
1268
|
-
var SignupFormEmbed =
|
|
1269
|
-
hosted_url:
|
|
1391
|
+
var SignupFormEmbed = z8.object({
|
|
1392
|
+
hosted_url: z8.string().url(),
|
|
1270
1393
|
/** A plain HTML form posting to the hosted page; works without JavaScript. */
|
|
1271
|
-
html:
|
|
1394
|
+
html: z8.string(),
|
|
1272
1395
|
/** A small optional script that adds the time check and submits in place. */
|
|
1273
|
-
script_url:
|
|
1396
|
+
script_url: z8.string().url(),
|
|
1274
1397
|
/** Where a browser fetches a fresh `form_token` from (GET, public, CORS for `allowed_origins`). */
|
|
1275
|
-
token_url:
|
|
1398
|
+
token_url: z8.string().url()
|
|
1276
1399
|
});
|
|
1277
|
-
var SignupSubmission =
|
|
1400
|
+
var SignupSubmission = z8.object({
|
|
1278
1401
|
email: Email,
|
|
1279
|
-
first_name:
|
|
1280
|
-
last_name:
|
|
1281
|
-
locale:
|
|
1402
|
+
first_name: z8.string().max(200).optional(),
|
|
1403
|
+
last_name: z8.string().max(200).optional(),
|
|
1404
|
+
locale: z8.string().min(2).max(35).optional(),
|
|
1282
1405
|
/** Honeypot: must be empty. */
|
|
1283
|
-
website:
|
|
1406
|
+
website: z8.string().max(0).optional(),
|
|
1284
1407
|
/**
|
|
1285
1408
|
* The signed render time from the form's token URL: a submission sooner than a
|
|
1286
1409
|
* human could fill the form, or long after, is refused.
|
|
1287
1410
|
*/
|
|
1288
|
-
form_token:
|
|
1411
|
+
form_token: z8.string().min(1).max(512)
|
|
1289
1412
|
});
|
|
1290
1413
|
var IMPORT_STATUSES = ["uploaded", "validating", "validated", "committing", "completed", "failed", "cancelled"];
|
|
1291
|
-
var ImportStatus =
|
|
1414
|
+
var ImportStatus = z8.enum(IMPORT_STATUSES);
|
|
1292
1415
|
var IMPORT_TERMINAL_STATUSES = ["completed", "failed", "cancelled"];
|
|
1293
|
-
var ImportColumnMapping =
|
|
1294
|
-
|
|
1416
|
+
var ImportColumnMapping = z8.record(
|
|
1417
|
+
z8.string().regex(/^(email|external_id|first_name|last_name|locale|property:[A-Za-z0-9_.-]{1,64}|ignore)$/)
|
|
1295
1418
|
);
|
|
1296
1419
|
var IMPORT_FILE_FIELD = "file";
|
|
1297
1420
|
var MAX_IMPORT_BYTES = 50 * 1024 * 1024;
|
|
1298
1421
|
var MAX_IMPORT_ROWS = 2e5;
|
|
1299
|
-
var ImportMappingRequest =
|
|
1422
|
+
var ImportMappingRequest = z8.object({
|
|
1300
1423
|
/** CSV header -> contact field. Exactly one column maps to `email`. */
|
|
1301
1424
|
mapping: ImportColumnMapping.refine(
|
|
1302
1425
|
(m) => Object.values(m).filter((v) => v === "email").length === 1,
|
|
1303
1426
|
"exactly one column maps to email"
|
|
1304
1427
|
),
|
|
1305
|
-
topics:
|
|
1306
|
-
tags:
|
|
1428
|
+
topics: z8.array(Slug).max(100),
|
|
1429
|
+
tags: z8.array(Slug).max(100),
|
|
1307
1430
|
/** Existing contacts get the mapped fields overwritten; false only adds topics, tags and missing fields. */
|
|
1308
|
-
update_existing:
|
|
1431
|
+
update_existing: z8.boolean().optional(),
|
|
1309
1432
|
/** The importer states the people consented; the service records who said so and when. */
|
|
1310
|
-
consent_confirmed:
|
|
1433
|
+
consent_confirmed: z8.literal(true)
|
|
1311
1434
|
});
|
|
1312
|
-
var ImportCommitRequest =
|
|
1435
|
+
var ImportCommitRequest = z8.object({ mapping_version: z8.number().int().min(1) });
|
|
1313
1436
|
var IMPORT_ROW_OUTCOMES = ["created", "updated", "unchanged", "suppressed", "skipped"];
|
|
1314
|
-
var ImportRowOutcome =
|
|
1437
|
+
var ImportRowOutcome = z8.enum(IMPORT_ROW_OUTCOMES);
|
|
1315
1438
|
var IMPORT_SKIP_REASONS = [
|
|
1316
1439
|
"missing_email",
|
|
1317
1440
|
"invalid_email",
|
|
@@ -1320,90 +1443,90 @@ var IMPORT_SKIP_REASONS = [
|
|
|
1320
1443
|
"external_id_conflict",
|
|
1321
1444
|
"wrong_column_count"
|
|
1322
1445
|
];
|
|
1323
|
-
var ImportSkipReason =
|
|
1324
|
-
var ImportReport =
|
|
1325
|
-
created:
|
|
1326
|
-
updated:
|
|
1327
|
-
unchanged:
|
|
1328
|
-
suppressed:
|
|
1329
|
-
skipped:
|
|
1330
|
-
skipped_by_reason:
|
|
1446
|
+
var ImportSkipReason = z8.enum(IMPORT_SKIP_REASONS);
|
|
1447
|
+
var ImportReport = z8.object({
|
|
1448
|
+
created: z8.number().int().min(0),
|
|
1449
|
+
updated: z8.number().int().min(0),
|
|
1450
|
+
unchanged: z8.number().int().min(0),
|
|
1451
|
+
suppressed: z8.number().int().min(0),
|
|
1452
|
+
skipped: z8.number().int().min(0),
|
|
1453
|
+
skipped_by_reason: z8.record(ImportSkipReason, z8.number().int().min(0)),
|
|
1331
1454
|
/** Rows whose contact was subscribed without some topic, because the address is blocked for it. */
|
|
1332
|
-
topics_withheld:
|
|
1455
|
+
topics_withheld: z8.number().int().min(0)
|
|
1333
1456
|
});
|
|
1334
|
-
var ImportJob =
|
|
1457
|
+
var ImportJob = z8.object({
|
|
1335
1458
|
id: Id,
|
|
1336
1459
|
status: ImportStatus,
|
|
1337
|
-
file_name:
|
|
1338
|
-
file_bytes:
|
|
1339
|
-
total_rows:
|
|
1460
|
+
file_name: z8.string().max(255).nullable(),
|
|
1461
|
+
file_bytes: z8.number().int().min(0),
|
|
1462
|
+
total_rows: z8.number().int().min(0),
|
|
1340
1463
|
/** The header row, as in the file. */
|
|
1341
|
-
columns:
|
|
1464
|
+
columns: z8.array(z8.string()),
|
|
1342
1465
|
/** The first rows, for the mapping screen. */
|
|
1343
|
-
sample:
|
|
1466
|
+
sample: z8.array(z8.array(z8.string())).max(5),
|
|
1344
1467
|
/** A mapping guessed from the headers, for the mapping screen to start from. */
|
|
1345
1468
|
suggested_mapping: ImportColumnMapping,
|
|
1346
1469
|
mapping: ImportColumnMapping.nullable(),
|
|
1347
|
-
mapping_version:
|
|
1348
|
-
topics:
|
|
1349
|
-
tags:
|
|
1350
|
-
update_existing:
|
|
1470
|
+
mapping_version: z8.number().int().min(0),
|
|
1471
|
+
topics: z8.array(Slug),
|
|
1472
|
+
tags: z8.array(Slug),
|
|
1473
|
+
update_existing: z8.boolean(),
|
|
1351
1474
|
/** The dry run of the current mapping; null before one finished. */
|
|
1352
1475
|
dry_run: ImportReport.nullable(),
|
|
1353
1476
|
/** What the commit has written so far; null before it started. */
|
|
1354
1477
|
result: ImportReport.nullable(),
|
|
1355
1478
|
/** Rows the current phase (dry run or commit) has processed. */
|
|
1356
|
-
processed_rows:
|
|
1479
|
+
processed_rows: z8.number().int().min(0),
|
|
1357
1480
|
/** Why a `failed` import failed. */
|
|
1358
|
-
error:
|
|
1481
|
+
error: z8.string().nullable(),
|
|
1359
1482
|
created_at: Timestamp,
|
|
1360
1483
|
updated_at: Timestamp,
|
|
1361
1484
|
finished_at: Timestamp.nullable()
|
|
1362
1485
|
});
|
|
1363
|
-
var ImportRow =
|
|
1486
|
+
var ImportRow = z8.object({
|
|
1364
1487
|
/** The line in the file, 1 being the first line after the header. */
|
|
1365
|
-
row:
|
|
1366
|
-
email:
|
|
1488
|
+
row: z8.number().int().min(1),
|
|
1489
|
+
email: z8.string().nullable(),
|
|
1367
1490
|
outcome: ImportRowOutcome,
|
|
1368
1491
|
reason: ImportSkipReason.nullable(),
|
|
1369
1492
|
/** A human explanation for a skipped row (which value, which column). */
|
|
1370
|
-
message:
|
|
1493
|
+
message: z8.string().nullable(),
|
|
1371
1494
|
contact_id: Id.nullable()
|
|
1372
1495
|
});
|
|
1373
1496
|
var ImportRowListQuery = PageQuery.extend({ outcome: ImportRowOutcome.optional() });
|
|
1374
|
-
var MailingScheduleRequest =
|
|
1375
|
-
var TrackingSettings =
|
|
1376
|
-
var VariantAnalytics =
|
|
1377
|
-
key:
|
|
1378
|
-
sent:
|
|
1379
|
-
unique_opens:
|
|
1380
|
-
unique_clicks:
|
|
1381
|
-
});
|
|
1382
|
-
var MailingAnalytics =
|
|
1497
|
+
var MailingScheduleRequest = z8.object({ send_at: Timestamp });
|
|
1498
|
+
var TrackingSettings = z8.object({ opens: z8.boolean(), clicks: z8.boolean() });
|
|
1499
|
+
var VariantAnalytics = z8.object({
|
|
1500
|
+
key: z8.string(),
|
|
1501
|
+
sent: z8.number().int().min(0),
|
|
1502
|
+
unique_opens: z8.number().int().min(0).nullable(),
|
|
1503
|
+
unique_clicks: z8.number().int().min(0).nullable()
|
|
1504
|
+
});
|
|
1505
|
+
var MailingAnalytics = z8.object({
|
|
1383
1506
|
mailing_id: Id,
|
|
1384
1507
|
counts: MailingCounts,
|
|
1385
1508
|
/** What was tracked for this mailing, fixed when it started; null before it started. */
|
|
1386
1509
|
tracking: TrackingSettings.nullable(),
|
|
1387
1510
|
/** Unique recipients with a human open. Null when opens were not tracked. */
|
|
1388
|
-
unique_opens:
|
|
1511
|
+
unique_opens: z8.number().int().min(0).nullable(),
|
|
1389
1512
|
/**
|
|
1390
1513
|
* Recipients whose only opens came from Apple Mail Privacy Protection, which
|
|
1391
1514
|
* loads every image on delivery: counted apart, never as human opens.
|
|
1392
1515
|
*/
|
|
1393
|
-
apple_mpp_opens:
|
|
1516
|
+
apple_mpp_opens: z8.number().int().min(0).nullable(),
|
|
1394
1517
|
/** Opens and clicks from security scanners and prefetchers, filtered out of the unique counts. */
|
|
1395
|
-
machine_events:
|
|
1396
|
-
unique_clicks:
|
|
1397
|
-
unsubscribes:
|
|
1398
|
-
bounces:
|
|
1399
|
-
complaints:
|
|
1400
|
-
links:
|
|
1401
|
-
variants:
|
|
1518
|
+
machine_events: z8.number().int().min(0).nullable(),
|
|
1519
|
+
unique_clicks: z8.number().int().min(0).nullable(),
|
|
1520
|
+
unsubscribes: z8.number().int().min(0),
|
|
1521
|
+
bounces: z8.number().int().min(0),
|
|
1522
|
+
complaints: z8.number().int().min(0),
|
|
1523
|
+
links: z8.array(z8.object({ url: z8.string(), unique_clicks: z8.number().int().min(0) })).nullable(),
|
|
1524
|
+
variants: z8.array(VariantAnalytics).nullable()
|
|
1402
1525
|
});
|
|
1403
1526
|
|
|
1404
1527
|
// src/automations.ts
|
|
1405
1528
|
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})$/;
|
|
1406
|
-
var AutomationFilterField =
|
|
1529
|
+
var AutomationFilterField = z9.string().regex(AUTOMATION_FILTER_FIELD_PATTERN, "unknown filter field");
|
|
1407
1530
|
var AUTOMATION_FILTER_FIELD_OPERATORS = {
|
|
1408
1531
|
...FILTER_FIELD_OPERATORS,
|
|
1409
1532
|
segment: ["eq", "neq", "in", "not_in"],
|
|
@@ -1413,18 +1536,18 @@ var AUTOMATION_FILTER_FIELD_OPERATORS = {
|
|
|
1413
1536
|
step: ["eq", "neq"]
|
|
1414
1537
|
};
|
|
1415
1538
|
var automationFilterFamily = filterFieldFamily;
|
|
1416
|
-
var AutomationFilterCondition =
|
|
1539
|
+
var AutomationFilterCondition = z9.object({ field: AutomationFilterField, op: FilterOperator, value: JsonValue.optional() }).strict().refine((c) => (c.op === "exists" || c.op === "not_exists") === (c.value === void 0), {
|
|
1417
1540
|
message: "exists and not_exists take no value; every other operator needs one",
|
|
1418
1541
|
path: ["value"]
|
|
1419
1542
|
}).refine((c) => c.op !== "in" && c.op !== "not_in" || Array.isArray(c.value), {
|
|
1420
1543
|
message: "in and not_in take an array",
|
|
1421
1544
|
path: ["value"]
|
|
1422
1545
|
});
|
|
1423
|
-
var AutomationFilter =
|
|
1424
|
-
() =>
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1546
|
+
var AutomationFilter = z9.lazy(
|
|
1547
|
+
() => z9.union([
|
|
1548
|
+
z9.object({ and: z9.array(AutomationFilter).min(1).max(50) }).strict(),
|
|
1549
|
+
z9.object({ or: z9.array(AutomationFilter).min(1).max(50) }).strict(),
|
|
1550
|
+
z9.object({ not: AutomationFilter }).strict(),
|
|
1428
1551
|
AutomationFilterCondition
|
|
1429
1552
|
])
|
|
1430
1553
|
);
|
|
@@ -1437,20 +1560,20 @@ function filterConditions(filter) {
|
|
|
1437
1560
|
if ("not" in filter) return filterConditions(filter.not);
|
|
1438
1561
|
return [filter];
|
|
1439
1562
|
}
|
|
1440
|
-
var StepId =
|
|
1441
|
-
var TimeOfDay =
|
|
1442
|
-
var Weekday =
|
|
1443
|
-
var TimezoneName =
|
|
1563
|
+
var StepId = z9.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "letters, digits, underscore and hyphen, 1 to 64");
|
|
1564
|
+
var TimeOfDay = z9.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "HH:MM in 24 hours");
|
|
1565
|
+
var Weekday = z9.number().int().min(0).max(6);
|
|
1566
|
+
var TimezoneName = z9.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");
|
|
1444
1567
|
var MAX_DELAY_SECONDS = 365 * 24 * 60 * 60;
|
|
1445
|
-
var DateOnly =
|
|
1568
|
+
var DateOnly = z9.string().regex(/^\d{4}-\d{2}-\d{2}$/, "a calendar date such as 2026-09-20").refine((v) => {
|
|
1446
1569
|
const [y, m, d] = v.split("-").map(Number);
|
|
1447
1570
|
const t = new Date(Date.UTC(y, m - 1, d));
|
|
1448
1571
|
return t.getUTCFullYear() === y && t.getUTCMonth() + 1 === m && t.getUTCDate() === d;
|
|
1449
1572
|
}, "a real calendar date");
|
|
1450
|
-
var OffsetDays =
|
|
1573
|
+
var OffsetDays = z9.number().int().min(-365).max(365);
|
|
1451
1574
|
var DATE_PASSED_ANSWERS = ["skip", "stop", "continue"];
|
|
1452
|
-
var DatePassedAnswer =
|
|
1453
|
-
var EventName =
|
|
1575
|
+
var DatePassedAnswer = z9.enum(DATE_PASSED_ANSWERS);
|
|
1576
|
+
var EventName = z9.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$/, "letters, digits, dot, colon, underscore and hyphen, 1 to 80");
|
|
1454
1577
|
var AUTOMATION_TRIGGER_KINDS = [
|
|
1455
1578
|
"topic_subscribed",
|
|
1456
1579
|
"form_confirmed",
|
|
@@ -1463,17 +1586,17 @@ var AUTOMATION_TRIGGER_KINDS = [
|
|
|
1463
1586
|
"segment_entered",
|
|
1464
1587
|
"link_clicked"
|
|
1465
1588
|
];
|
|
1466
|
-
var AutomationTriggerKind =
|
|
1589
|
+
var AutomationTriggerKind = z9.enum(AUTOMATION_TRIGGER_KINDS);
|
|
1467
1590
|
var exclude = { exclude: BoundedAutomationFilter.nullish() };
|
|
1468
|
-
var AutomationTrigger =
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1591
|
+
var AutomationTrigger = z9.discriminatedUnion("kind", [
|
|
1592
|
+
z9.object({ kind: z9.literal("topic_subscribed"), topic: Slug, ...exclude }).strict(),
|
|
1593
|
+
z9.object({ kind: z9.literal("form_confirmed"), form_id: Id, ...exclude }).strict(),
|
|
1594
|
+
z9.object({ kind: z9.literal("tag_added"), tag: Slug, ...exclude }).strict(),
|
|
1472
1595
|
/** `to` given: only a change that lands on this value fires. Absent: any change of the key. */
|
|
1473
|
-
|
|
1596
|
+
z9.object({ kind: z9.literal("property_changed"), key: ContactPropertyKey, to: JsonValue.optional(), ...exclude }).strict(),
|
|
1474
1597
|
/** `where` filters the event's own data, so only `event:<key>` leaves belong in it. */
|
|
1475
|
-
|
|
1476
|
-
|
|
1598
|
+
z9.object({ kind: z9.literal("event"), name: EventName, where: BoundedAutomationFilter.nullish(), ...exclude }).strict(),
|
|
1599
|
+
z9.object({ kind: z9.literal("manual"), ...exclude }).strict(),
|
|
1477
1600
|
/**
|
|
1478
1601
|
* Every year on a date the contact carries: a birthday, a joining date, a
|
|
1479
1602
|
* renewal. `offset_days` moves it ("three days before"), `at` is the wall
|
|
@@ -1483,8 +1606,8 @@ var AutomationTrigger = z8.discriminatedUnion("kind", [
|
|
|
1483
1606
|
* A 29 February date fires on 1 March in a year that has no 29 February, so
|
|
1484
1607
|
* nobody is skipped every fourth year.
|
|
1485
1608
|
*/
|
|
1486
|
-
|
|
1487
|
-
kind:
|
|
1609
|
+
z9.object({
|
|
1610
|
+
kind: z9.literal("date_anniversary"),
|
|
1488
1611
|
key: ContactPropertyKey,
|
|
1489
1612
|
offset_days: OffsetDays,
|
|
1490
1613
|
at: TimeOfDay,
|
|
@@ -1496,8 +1619,8 @@ var AutomationTrigger = z8.discriminatedUnion("kind", [
|
|
|
1496
1619
|
* not ask about a segment or about an event's data: there is no event, and a
|
|
1497
1620
|
* segment is its own trigger kind.
|
|
1498
1621
|
*/
|
|
1499
|
-
|
|
1500
|
-
kind:
|
|
1622
|
+
z9.object({
|
|
1623
|
+
kind: z9.literal("exact_date"),
|
|
1501
1624
|
on: DateOnly,
|
|
1502
1625
|
at: TimeOfDay,
|
|
1503
1626
|
where: BoundedAutomationFilter.nullish(),
|
|
@@ -1516,7 +1639,7 @@ var AutomationTrigger = z8.discriminatedUnion("kind", [
|
|
|
1516
1639
|
* already exists. Entering those people as well is the publish dialog's
|
|
1517
1640
|
* backfill question, answered by a person rather than by the sweep.
|
|
1518
1641
|
*/
|
|
1519
|
-
|
|
1642
|
+
z9.object({ kind: z9.literal("segment_entered"), segment_id: Id, ...exclude }).strict(),
|
|
1520
1643
|
/**
|
|
1521
1644
|
* S4. When somebody clicks a link in a mailing that has already gone out.
|
|
1522
1645
|
*
|
|
@@ -1526,7 +1649,7 @@ var AutomationTrigger = z8.discriminatedUnion("kind", [
|
|
|
1526
1649
|
* tracking off there is no click to hear about, so the validator refuses it
|
|
1527
1650
|
* rather than leaving an automation that can never fire.
|
|
1528
1651
|
*/
|
|
1529
|
-
|
|
1652
|
+
z9.object({ kind: z9.literal("link_clicked"), mailing_id: Id, url: z9.string().min(1).max(2048).optional(), ...exclude }).strict()
|
|
1530
1653
|
]);
|
|
1531
1654
|
var AUTOMATION_DATE_TRIGGER_KINDS = ["date_anniversary", "exact_date"];
|
|
1532
1655
|
var MAX_TRIGGERS = 10;
|
|
@@ -1545,45 +1668,45 @@ var AUTOMATION_STEP_KINDS = [
|
|
|
1545
1668
|
"webhook",
|
|
1546
1669
|
"notify"
|
|
1547
1670
|
];
|
|
1548
|
-
var AutomationStepKind =
|
|
1671
|
+
var AutomationStepKind = z9.enum(AUTOMATION_STEP_KINDS);
|
|
1549
1672
|
var next = { next: StepId.nullable() };
|
|
1550
|
-
var Unchosen =
|
|
1551
|
-
var DraftSlug =
|
|
1552
|
-
var DraftPropertyKey =
|
|
1553
|
-
var
|
|
1554
|
-
var EmailStepLanguage =
|
|
1555
|
-
subject:
|
|
1556
|
-
preheader:
|
|
1673
|
+
var Unchosen = z9.literal("");
|
|
1674
|
+
var DraftSlug = z9.union([Slug, Unchosen]);
|
|
1675
|
+
var DraftPropertyKey = z9.union([ContactPropertyKey, Unchosen]);
|
|
1676
|
+
var label2 = { label: z9.string().max(120).nullish() };
|
|
1677
|
+
var EmailStepLanguage = z9.object({
|
|
1678
|
+
subject: z9.string().min(1).max(998),
|
|
1679
|
+
preheader: z9.string().max(500).nullable(),
|
|
1557
1680
|
document: TemplateDocument,
|
|
1558
1681
|
/** This language's insertions, its texts over the template's structure (every text present). */
|
|
1559
1682
|
insertions: Insertions.nullish()
|
|
1560
1683
|
}).strict();
|
|
1561
|
-
var EmailStep =
|
|
1562
|
-
kind:
|
|
1563
|
-
...
|
|
1684
|
+
var EmailStep = z9.object({
|
|
1685
|
+
kind: z9.literal("email"),
|
|
1686
|
+
...label2,
|
|
1564
1687
|
...next,
|
|
1565
1688
|
template_id: Id.nullish(),
|
|
1566
1689
|
document: TemplateDocument.nullish(),
|
|
1567
1690
|
/** Empty while the step is being written, or when its template has a subject; else `not_chosen` refuses to publish it. */
|
|
1568
|
-
subject:
|
|
1569
|
-
preheader:
|
|
1691
|
+
subject: z9.string().max(998),
|
|
1692
|
+
preheader: z9.string().max(500).nullish(),
|
|
1570
1693
|
locale: LocaleTag.nullish(),
|
|
1571
|
-
languages:
|
|
1694
|
+
languages: z9.record(LocaleTag, EmailStepLanguage).nullish(),
|
|
1572
1695
|
/** The main language's insertions, snapshot from the template when the automation is published. */
|
|
1573
1696
|
insertions: Insertions.nullish()
|
|
1574
1697
|
}).strict();
|
|
1575
|
-
var DelayStep =
|
|
1576
|
-
kind:
|
|
1577
|
-
...
|
|
1698
|
+
var DelayStep = z9.object({
|
|
1699
|
+
kind: z9.literal("delay"),
|
|
1700
|
+
...label2,
|
|
1578
1701
|
...next,
|
|
1579
|
-
wait:
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1702
|
+
wait: z9.discriminatedUnion("mode", [
|
|
1703
|
+
z9.object({ mode: z9.literal("duration"), seconds: z9.number().int().min(1).max(MAX_DELAY_SECONDS) }).strict(),
|
|
1704
|
+
z9.object({ mode: z9.literal("time_of_day"), at: TimeOfDay }).strict(),
|
|
1705
|
+
z9.object({ mode: z9.literal("weekday"), weekday: Weekday, at: TimeOfDay }).strict(),
|
|
1583
1706
|
/** A fixed day has no next occurrence, so `continue` is not one of its answers. */
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
mode:
|
|
1707
|
+
z9.object({ mode: z9.literal("date"), on: DateOnly, at: TimeOfDay, passed: z9.enum(["skip", "stop"]) }).strict(),
|
|
1708
|
+
z9.object({
|
|
1709
|
+
mode: z9.literal("date_property"),
|
|
1587
1710
|
key: ContactPropertyKey,
|
|
1588
1711
|
offset_days: OffsetDays,
|
|
1589
1712
|
at: TimeOfDay,
|
|
@@ -1591,9 +1714,9 @@ var DelayStep = z8.object({
|
|
|
1591
1714
|
}).strict()
|
|
1592
1715
|
])
|
|
1593
1716
|
}).strict();
|
|
1594
|
-
var ConditionStep =
|
|
1595
|
-
kind:
|
|
1596
|
-
...
|
|
1717
|
+
var ConditionStep = z9.object({
|
|
1718
|
+
kind: z9.literal("condition"),
|
|
1719
|
+
...label2,
|
|
1597
1720
|
filter: BoundedAutomationFilter,
|
|
1598
1721
|
yes: StepId.nullable(),
|
|
1599
1722
|
no: StepId.nullable(),
|
|
@@ -1603,49 +1726,49 @@ var ConditionStep = z8.object({
|
|
|
1603
1726
|
* neither half means anything without the other: a window with no answer
|
|
1604
1727
|
* for the end of it would be a silent decision.
|
|
1605
1728
|
*/
|
|
1606
|
-
wait_for:
|
|
1729
|
+
wait_for: z9.object({ up_to_s: z9.number().int().min(1).max(MAX_DELAY_SECONDS), on_timeout: z9.enum(["yes", "no"]) }).strict().nullish()
|
|
1607
1730
|
}).strict();
|
|
1608
|
-
var SplitBranchKey =
|
|
1731
|
+
var SplitBranchKey = z9.string().regex(/^[A-Za-z0-9_-]{1,32}$/, "letters, digits, underscore and hyphen, 1 to 32");
|
|
1609
1732
|
var MIN_SPLIT_BRANCHES = 2;
|
|
1610
1733
|
var MAX_SPLIT_BRANCHES = 4;
|
|
1611
|
-
var SplitStep =
|
|
1612
|
-
kind:
|
|
1613
|
-
...
|
|
1614
|
-
branches:
|
|
1615
|
-
|
|
1734
|
+
var SplitStep = z9.object({
|
|
1735
|
+
kind: z9.literal("split"),
|
|
1736
|
+
...label2,
|
|
1737
|
+
branches: z9.array(
|
|
1738
|
+
z9.object({
|
|
1616
1739
|
key: SplitBranchKey,
|
|
1617
1740
|
/** A whole percentage. Zero is allowed: a branch kept in the flow but switched off. */
|
|
1618
|
-
weight:
|
|
1741
|
+
weight: z9.number().int().min(0).max(100),
|
|
1619
1742
|
next: StepId.nullable()
|
|
1620
1743
|
}).strict()
|
|
1621
1744
|
).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")
|
|
1622
1745
|
}).strict();
|
|
1623
|
-
var GotoStep =
|
|
1746
|
+
var GotoStep = z9.object({ kind: z9.literal("goto"), ...label2, target: StepId.nullable() }).strict();
|
|
1624
1747
|
var MAX_STEP_VISITS = 20;
|
|
1625
|
-
var WebhookStep =
|
|
1626
|
-
var NotifyRecipients =
|
|
1627
|
-
var NotifyStep =
|
|
1628
|
-
kind:
|
|
1629
|
-
...
|
|
1748
|
+
var WebhookStep = z9.object({ kind: z9.literal("webhook"), ...label2, ...next, endpoint_id: Id.nullable(), payload: JsonValue.nullish() }).strict();
|
|
1749
|
+
var NotifyRecipients = z9.union([z9.literal("owner"), z9.literal("admins"), z9.object({ member_id: Id }).strict()]);
|
|
1750
|
+
var NotifyStep = z9.object({
|
|
1751
|
+
kind: z9.literal("notify"),
|
|
1752
|
+
...label2,
|
|
1630
1753
|
...next,
|
|
1631
1754
|
to: NotifyRecipients,
|
|
1632
1755
|
/** Empty while the step is being written; `not_chosen` refuses to publish it. */
|
|
1633
|
-
subject:
|
|
1634
|
-
body:
|
|
1756
|
+
subject: z9.string().max(998),
|
|
1757
|
+
body: z9.string().max(1e4)
|
|
1635
1758
|
}).strict();
|
|
1636
|
-
var AutomationStep =
|
|
1759
|
+
var AutomationStep = z9.discriminatedUnion("kind", [
|
|
1637
1760
|
EmailStep,
|
|
1638
1761
|
DelayStep,
|
|
1639
1762
|
ConditionStep,
|
|
1640
|
-
|
|
1641
|
-
|
|
1763
|
+
z9.object({ kind: z9.literal("add_tag"), ...label2, ...next, tag: DraftSlug }).strict(),
|
|
1764
|
+
z9.object({ kind: z9.literal("remove_tag"), ...label2, ...next, tag: DraftSlug }).strict(),
|
|
1642
1765
|
/** `value` null clears the property, as a contact upsert does. */
|
|
1643
|
-
|
|
1766
|
+
z9.object({ kind: z9.literal("set_property"), ...label2, ...next, key: DraftPropertyKey, value: JsonValue }).strict(),
|
|
1644
1767
|
/** Subscribing never lifts a suppression: the service checks that, not the flow. */
|
|
1645
|
-
|
|
1768
|
+
z9.object({ kind: z9.literal("subscribe_topic"), ...label2, ...next, topic: DraftSlug }).strict(),
|
|
1646
1769
|
/** `topic` unsubscribes from the automation's own topic, `all` from every topic. */
|
|
1647
|
-
|
|
1648
|
-
|
|
1770
|
+
z9.object({ kind: z9.literal("unsubscribe"), ...label2, ...next, scope: z9.enum(["topic", "all"]) }).strict(),
|
|
1771
|
+
z9.object({ kind: z9.literal("exit"), ...label2 }).strict(),
|
|
1649
1772
|
SplitStep,
|
|
1650
1773
|
GotoStep,
|
|
1651
1774
|
WebhookStep,
|
|
@@ -1669,25 +1792,25 @@ function stepTargets(step) {
|
|
|
1669
1792
|
}
|
|
1670
1793
|
}
|
|
1671
1794
|
var MAX_STEPS = 200;
|
|
1672
|
-
var AutomationFlow =
|
|
1673
|
-
triggers:
|
|
1795
|
+
var AutomationFlow = z9.object({
|
|
1796
|
+
triggers: z9.array(AutomationTrigger).max(MAX_TRIGGERS),
|
|
1674
1797
|
/** The first step. `null` is an empty flow. */
|
|
1675
1798
|
entry: StepId.nullable(),
|
|
1676
|
-
steps:
|
|
1799
|
+
steps: z9.record(StepId, AutomationStep)
|
|
1677
1800
|
}).strict();
|
|
1678
1801
|
var emptyFlow = () => ({ triggers: [], entry: null, steps: {} });
|
|
1679
|
-
var QuietHours =
|
|
1802
|
+
var QuietHours = z9.object({
|
|
1680
1803
|
start: TimeOfDay,
|
|
1681
1804
|
end: TimeOfDay,
|
|
1682
|
-
weekdays:
|
|
1805
|
+
weekdays: z9.array(Weekday).min(1).max(7).nullish()
|
|
1683
1806
|
}).strict();
|
|
1684
1807
|
var RE_ENTRY_MODES = ["off", "after_exit"];
|
|
1685
|
-
var ReEntryMode =
|
|
1808
|
+
var ReEntryMode = z9.enum(RE_ENTRY_MODES);
|
|
1686
1809
|
var MAX_RE_ENTRY_COOLDOWN_SECONDS = 365 * 24 * 60 * 60;
|
|
1687
|
-
var AutomationSettings =
|
|
1810
|
+
var AutomationSettings = z9.object({
|
|
1688
1811
|
re_entry: ReEntryMode,
|
|
1689
1812
|
/** Seconds a person must wait after leaving before entering again. Only with `after_exit`. */
|
|
1690
|
-
re_entry_cooldown_s:
|
|
1813
|
+
re_entry_cooldown_s: z9.number().int().min(0).max(MAX_RE_ENTRY_COOLDOWN_SECONDS).nullish(),
|
|
1691
1814
|
quiet_hours: QuietHours.nullish(),
|
|
1692
1815
|
/** Checked before every step; when it matches, the run ends. */
|
|
1693
1816
|
exit_filter: BoundedAutomationFilter.nullish()
|
|
@@ -1745,18 +1868,18 @@ var AUTOMATION_ISSUE_CODES = [
|
|
|
1745
1868
|
/** A one-off date that is already over everywhere on earth, so it would fire for nobody. */
|
|
1746
1869
|
"date_in_the_past"
|
|
1747
1870
|
];
|
|
1748
|
-
var AutomationIssueCode =
|
|
1749
|
-
var AutomationIssue =
|
|
1871
|
+
var AutomationIssueCode = z9.enum(AUTOMATION_ISSUE_CODES);
|
|
1872
|
+
var AutomationIssue = z9.object({
|
|
1750
1873
|
code: AutomationIssueCode,
|
|
1751
1874
|
/** The step the issue is about, when it is about one. */
|
|
1752
1875
|
step_id: StepId.nullable(),
|
|
1753
1876
|
/** Where inside the document, for the canvas to point at a field. */
|
|
1754
|
-
path:
|
|
1755
|
-
message:
|
|
1877
|
+
path: z9.array(z9.union([z9.string(), z9.number()])),
|
|
1878
|
+
message: z9.string()
|
|
1756
1879
|
});
|
|
1757
|
-
var AutomationValidation =
|
|
1758
|
-
ok:
|
|
1759
|
-
issues:
|
|
1880
|
+
var AutomationValidation = z9.object({
|
|
1881
|
+
ok: z9.boolean(),
|
|
1882
|
+
issues: z9.array(AutomationIssue)
|
|
1760
1883
|
});
|
|
1761
1884
|
function validateAutomation(flow, settings, ctx) {
|
|
1762
1885
|
const issues = [];
|
|
@@ -2147,7 +2270,7 @@ function cycles(flow) {
|
|
|
2147
2270
|
return out.sort();
|
|
2148
2271
|
}
|
|
2149
2272
|
var AUTOMATION_STATUSES = ["draft", "active", "paused", "archived"];
|
|
2150
|
-
var AutomationStatus =
|
|
2273
|
+
var AutomationStatus = z9.enum(AUTOMATION_STATUSES);
|
|
2151
2274
|
var AUTOMATION_END_REASONS = [
|
|
2152
2275
|
"completed",
|
|
2153
2276
|
"exit_filter",
|
|
@@ -2166,9 +2289,9 @@ var AUTOMATION_END_REASONS = [
|
|
|
2166
2289
|
"ended_by_hand",
|
|
2167
2290
|
"failed"
|
|
2168
2291
|
];
|
|
2169
|
-
var AutomationEndReason =
|
|
2292
|
+
var AutomationEndReason = z9.enum(AUTOMATION_END_REASONS);
|
|
2170
2293
|
var AUTOMATION_WAIT_REASONS = ["delay", "quiet_hours", "plan_limit", "sending", "condition"];
|
|
2171
|
-
var AutomationWaitReason =
|
|
2294
|
+
var AutomationWaitReason = z9.enum(AUTOMATION_WAIT_REASONS);
|
|
2172
2295
|
var AUTOMATION_ENTRY_REFUSALS = [
|
|
2173
2296
|
"already_inside",
|
|
2174
2297
|
"re_entry_off",
|
|
@@ -2179,26 +2302,26 @@ var AUTOMATION_ENTRY_REFUSALS = [
|
|
|
2179
2302
|
"not_active",
|
|
2180
2303
|
"unknown_contact"
|
|
2181
2304
|
];
|
|
2182
|
-
var AutomationEntryRefusal =
|
|
2183
|
-
var AutomationCounts =
|
|
2184
|
-
started:
|
|
2185
|
-
in_progress:
|
|
2186
|
-
completed:
|
|
2187
|
-
exited:
|
|
2188
|
-
failed:
|
|
2189
|
-
});
|
|
2190
|
-
var Automation =
|
|
2305
|
+
var AutomationEntryRefusal = z9.enum(AUTOMATION_ENTRY_REFUSALS);
|
|
2306
|
+
var AutomationCounts = z9.object({
|
|
2307
|
+
started: z9.number().int().min(0),
|
|
2308
|
+
in_progress: z9.number().int().min(0),
|
|
2309
|
+
completed: z9.number().int().min(0),
|
|
2310
|
+
exited: z9.number().int().min(0),
|
|
2311
|
+
failed: z9.number().int().min(0)
|
|
2312
|
+
});
|
|
2313
|
+
var Automation = z9.object({
|
|
2191
2314
|
id: Id,
|
|
2192
|
-
name:
|
|
2315
|
+
name: z9.string().min(1).max(200),
|
|
2193
2316
|
status: AutomationStatus,
|
|
2194
2317
|
/** Every automation sends under one topic; there are no transactional automations. */
|
|
2195
2318
|
topic: Slug,
|
|
2196
2319
|
provider_id: Id.nullable(),
|
|
2197
2320
|
settings: AutomationSettings,
|
|
2198
2321
|
/** The flow people are running through, null while the automation has never been published. */
|
|
2199
|
-
published_version:
|
|
2322
|
+
published_version: z9.number().int().min(1).nullable(),
|
|
2200
2323
|
/** The flow being edited. Increments on every save, like a template's version. */
|
|
2201
|
-
draft_version:
|
|
2324
|
+
draft_version: z9.number().int().min(1),
|
|
2202
2325
|
counts: AutomationCounts,
|
|
2203
2326
|
created_at: Timestamp,
|
|
2204
2327
|
updated_at: Timestamp,
|
|
@@ -2211,16 +2334,16 @@ var AutomationWithFlow = Automation.extend({
|
|
|
2211
2334
|
published_flow: AutomationFlow.nullable()
|
|
2212
2335
|
});
|
|
2213
2336
|
var AutomationSummary = Automation;
|
|
2214
|
-
var AutomationCreate =
|
|
2215
|
-
name:
|
|
2337
|
+
var AutomationCreate = z9.object({
|
|
2338
|
+
name: z9.string().min(1).max(200),
|
|
2216
2339
|
topic: Slug,
|
|
2217
2340
|
provider_id: Id.optional(),
|
|
2218
2341
|
flow: AutomationFlow.optional(),
|
|
2219
2342
|
settings: AutomationSettings.partial().optional()
|
|
2220
2343
|
});
|
|
2221
|
-
var AutomationUpdate =
|
|
2222
|
-
base_version:
|
|
2223
|
-
name:
|
|
2344
|
+
var AutomationUpdate = z9.object({
|
|
2345
|
+
base_version: z9.number().int().min(1),
|
|
2346
|
+
name: z9.string().min(1).max(200).optional(),
|
|
2224
2347
|
topic: Slug.optional(),
|
|
2225
2348
|
provider_id: Id.optional(),
|
|
2226
2349
|
flow: AutomationFlow.optional(),
|
|
@@ -2233,42 +2356,42 @@ var AutomationListQuery = PageQuery.extend({
|
|
|
2233
2356
|
status: AutomationStatus.optional(),
|
|
2234
2357
|
topic: Slug.optional()
|
|
2235
2358
|
});
|
|
2236
|
-
var AutomationPublishRequest =
|
|
2359
|
+
var AutomationPublishRequest = z9.object({
|
|
2237
2360
|
/**
|
|
2238
2361
|
* Answer what this publish would do and change nothing, whatever the flow
|
|
2239
2362
|
* looks like. Without it a publish that has no issues and moves nobody
|
|
2240
2363
|
* simply goes ahead, which is right for an API caller and wrong for a
|
|
2241
2364
|
* dialog that is only showing somebody what they are about to do.
|
|
2242
2365
|
*/
|
|
2243
|
-
dry_run:
|
|
2244
|
-
confirm:
|
|
2366
|
+
dry_run: z9.boolean().optional(),
|
|
2367
|
+
confirm: z9.boolean().optional(),
|
|
2245
2368
|
/** Per removed step: the step its waiting runs move to, or null to end them. */
|
|
2246
|
-
moves:
|
|
2369
|
+
moves: z9.record(StepId, StepId.nullable()).optional(),
|
|
2247
2370
|
/** Also enter everyone who matches a trigger's condition right now. */
|
|
2248
|
-
backfill:
|
|
2371
|
+
backfill: z9.boolean().optional()
|
|
2249
2372
|
}).strict();
|
|
2250
|
-
var AutomationPublishMove =
|
|
2373
|
+
var AutomationPublishMove = z9.object({
|
|
2251
2374
|
step_id: StepId,
|
|
2252
2375
|
/** Why the step's people have to move: it is gone, or its delay changed. */
|
|
2253
|
-
change:
|
|
2254
|
-
waiting:
|
|
2376
|
+
change: z9.enum(["removed", "delay_changed", "kind_changed"]),
|
|
2377
|
+
waiting: z9.number().int().min(0),
|
|
2255
2378
|
/** Where they go: a step id, or null to end their runs with `step_removed`. */
|
|
2256
2379
|
destination: StepId.nullable()
|
|
2257
2380
|
});
|
|
2258
|
-
var AutomationPublishResult =
|
|
2259
|
-
published:
|
|
2381
|
+
var AutomationPublishResult = z9.object({
|
|
2382
|
+
published: z9.boolean(),
|
|
2260
2383
|
automation: Automation,
|
|
2261
2384
|
/** Empty when nothing has to move; non-empty and `published` false asks for a confirmation. */
|
|
2262
|
-
moves:
|
|
2385
|
+
moves: z9.array(AutomationPublishMove),
|
|
2263
2386
|
/** How many people the backfill entered, or would enter when it is not confirmed. */
|
|
2264
|
-
backfill:
|
|
2265
|
-
issues:
|
|
2266
|
-
});
|
|
2267
|
-
var AutomationDuplicateRequest =
|
|
2268
|
-
var AutomationEnrollRequest =
|
|
2269
|
-
contact_ids:
|
|
2270
|
-
external_ids:
|
|
2271
|
-
emails:
|
|
2387
|
+
backfill: z9.number().int().min(0),
|
|
2388
|
+
issues: z9.array(AutomationIssue)
|
|
2389
|
+
});
|
|
2390
|
+
var AutomationDuplicateRequest = z9.object({ name: z9.string().min(1).max(200).optional() }).strict();
|
|
2391
|
+
var AutomationEnrollRequest = z9.object({
|
|
2392
|
+
contact_ids: z9.array(Id).min(1).max(1e3).optional(),
|
|
2393
|
+
external_ids: z9.array(z9.string().min(1).max(255)).min(1).max(1e3).optional(),
|
|
2394
|
+
emails: z9.array(Email).min(1).max(1e3).optional(),
|
|
2272
2395
|
segment_id: Id.optional(),
|
|
2273
2396
|
/** Where to start them; the flow's entry step by default. */
|
|
2274
2397
|
step_id: StepId.optional()
|
|
@@ -2276,34 +2399,34 @@ var AutomationEnrollRequest = z8.object({
|
|
|
2276
2399
|
(v) => [v.contact_ids, v.external_ids, v.emails, v.segment_id].filter((x) => x !== void 0).length === 1,
|
|
2277
2400
|
"name the people exactly one way: contact_ids, external_ids, emails or segment_id"
|
|
2278
2401
|
);
|
|
2279
|
-
var AutomationEnrollment =
|
|
2402
|
+
var AutomationEnrollment = z9.object({
|
|
2280
2403
|
contact_id: Id.nullable(),
|
|
2281
2404
|
email: Email.nullable(),
|
|
2282
|
-
external_id:
|
|
2283
|
-
entered:
|
|
2405
|
+
external_id: z9.string().nullable(),
|
|
2406
|
+
entered: z9.boolean(),
|
|
2284
2407
|
run_id: Id.nullable(),
|
|
2285
2408
|
reason: AutomationEntryRefusal.nullable()
|
|
2286
2409
|
});
|
|
2287
|
-
var AutomationEnrollResult =
|
|
2288
|
-
entered:
|
|
2289
|
-
refused:
|
|
2290
|
-
results:
|
|
2410
|
+
var AutomationEnrollResult = z9.object({
|
|
2411
|
+
entered: z9.number().int().min(0),
|
|
2412
|
+
refused: z9.number().int().min(0),
|
|
2413
|
+
results: z9.array(AutomationEnrollment)
|
|
2291
2414
|
});
|
|
2292
|
-
var AutomationRunStatus =
|
|
2293
|
-
var AutomationRun =
|
|
2415
|
+
var AutomationRunStatus = z9.enum(["active", "waiting", "completed", "exited", "failed"]);
|
|
2416
|
+
var AutomationRun = z9.object({
|
|
2294
2417
|
id: Id,
|
|
2295
2418
|
automation_id: Id,
|
|
2296
2419
|
contact_id: Id,
|
|
2297
2420
|
email: Email,
|
|
2298
|
-
external_id:
|
|
2299
|
-
version:
|
|
2421
|
+
external_id: z9.string().nullable(),
|
|
2422
|
+
version: z9.number().int().min(1),
|
|
2300
2423
|
status: AutomationRunStatus,
|
|
2301
2424
|
current_step_id: StepId.nullable(),
|
|
2302
2425
|
next_action_at: Timestamp.nullable(),
|
|
2303
2426
|
wait_reason: AutomationWaitReason.nullable(),
|
|
2304
|
-
end_reason:
|
|
2427
|
+
end_reason: z9.string().nullable(),
|
|
2305
2428
|
/** What went wrong the last time this run tried to take its step, null otherwise. */
|
|
2306
|
-
last_error:
|
|
2429
|
+
last_error: z9.string().nullable(),
|
|
2307
2430
|
entered_at: Timestamp,
|
|
2308
2431
|
ended_at: Timestamp.nullable()
|
|
2309
2432
|
});
|
|
@@ -2313,13 +2436,13 @@ var AutomationRunListQuery = PageQuery.extend({
|
|
|
2313
2436
|
contact_id: Id.optional()
|
|
2314
2437
|
});
|
|
2315
2438
|
var ContactAutomationRun = AutomationRun.extend({
|
|
2316
|
-
automation:
|
|
2439
|
+
automation: z9.object({ id: Id, name: z9.string(), status: AutomationStatus })
|
|
2317
2440
|
});
|
|
2318
2441
|
var ContactAutomationListQuery = PageQuery.extend({ status: AutomationRunStatus.optional() });
|
|
2319
|
-
var AutomationStepExecution =
|
|
2442
|
+
var AutomationStepExecution = z9.object({
|
|
2320
2443
|
step_id: StepId,
|
|
2321
2444
|
step_kind: AutomationStepKind,
|
|
2322
|
-
visit:
|
|
2445
|
+
visit: z9.number().int().min(1),
|
|
2323
2446
|
started_at: Timestamp,
|
|
2324
2447
|
finished_at: Timestamp.nullable(),
|
|
2325
2448
|
/**
|
|
@@ -2331,48 +2454,48 @@ var AutomationStepExecution = z8.object({
|
|
|
2331
2454
|
*/
|
|
2332
2455
|
outcome: Properties
|
|
2333
2456
|
});
|
|
2334
|
-
var AutomationJourney =
|
|
2457
|
+
var AutomationJourney = z9.object({
|
|
2335
2458
|
run: AutomationRun,
|
|
2336
|
-
steps:
|
|
2459
|
+
steps: z9.array(AutomationStepExecution)
|
|
2337
2460
|
});
|
|
2338
|
-
var AutomationStepReport =
|
|
2461
|
+
var AutomationStepReport = z9.object({
|
|
2339
2462
|
step_id: StepId,
|
|
2340
2463
|
step_kind: AutomationStepKind,
|
|
2341
|
-
reached:
|
|
2342
|
-
waiting:
|
|
2343
|
-
passed:
|
|
2464
|
+
reached: z9.number().int().min(0),
|
|
2465
|
+
waiting: z9.number().int().min(0),
|
|
2466
|
+
passed: z9.number().int().min(0),
|
|
2344
2467
|
/** Per branch, for a condition: how many took `yes` and how many `no`. */
|
|
2345
|
-
branches:
|
|
2468
|
+
branches: z9.record(z9.number().int().min(0)),
|
|
2346
2469
|
/** The step mailing's id, for an email step of the published version. */
|
|
2347
2470
|
mailing_id: Id.nullable()
|
|
2348
2471
|
});
|
|
2349
|
-
var AutomationReport =
|
|
2472
|
+
var AutomationReport = z9.object({
|
|
2350
2473
|
counts: AutomationCounts,
|
|
2351
2474
|
/** Why the people who left did: `AutomationEndReason` to a count. */
|
|
2352
|
-
end_reasons:
|
|
2475
|
+
end_reasons: z9.record(z9.number().int().min(0)),
|
|
2353
2476
|
/**
|
|
2354
2477
|
* Why people a trigger named did not enter: `AutomationEntryRefusal` to a
|
|
2355
2478
|
* count. Without it a pause, an exclusion or a missing subscription would
|
|
2356
2479
|
* turn people away invisibly.
|
|
2357
2480
|
*/
|
|
2358
|
-
refusals:
|
|
2359
|
-
steps:
|
|
2360
|
-
});
|
|
2361
|
-
var AutomationExitRunRequest =
|
|
2362
|
-
var AutomationRunParams =
|
|
2363
|
-
var AutomationStepParams =
|
|
2364
|
-
var AutomationTestEmailRequest =
|
|
2365
|
-
var AutomationValidateRequest =
|
|
2366
|
-
var EventCreate =
|
|
2481
|
+
refusals: z9.record(z9.number().int().min(0)),
|
|
2482
|
+
steps: z9.array(AutomationStepReport)
|
|
2483
|
+
});
|
|
2484
|
+
var AutomationExitRunRequest = z9.object({ reason: z9.string().max(200).optional() }).strict();
|
|
2485
|
+
var AutomationRunParams = z9.object({ id: Id, run_id: Id });
|
|
2486
|
+
var AutomationStepParams = z9.object({ id: Id, step_id: StepId });
|
|
2487
|
+
var AutomationTestEmailRequest = z9.object({ to: Email, merge: Properties.optional(), locale: LocaleTag.optional() }).strict();
|
|
2488
|
+
var AutomationValidateRequest = z9.object({ flow: AutomationFlow.optional(), settings: AutomationSettings.partial().optional() }).strict();
|
|
2489
|
+
var EventCreate = z9.object({
|
|
2367
2490
|
name: EventName,
|
|
2368
|
-
contact:
|
|
2491
|
+
contact: z9.object({ external_id: z9.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"),
|
|
2369
2492
|
data: Properties.optional(),
|
|
2370
2493
|
occurred_at: Timestamp.optional(),
|
|
2371
|
-
id:
|
|
2494
|
+
id: z9.string().min(1).max(128).optional()
|
|
2372
2495
|
}).strict();
|
|
2373
|
-
var Event =
|
|
2496
|
+
var Event = z9.object({
|
|
2374
2497
|
id: Id,
|
|
2375
|
-
client_event_id:
|
|
2498
|
+
client_event_id: z9.string().nullable(),
|
|
2376
2499
|
name: EventName,
|
|
2377
2500
|
contact_id: Id,
|
|
2378
2501
|
data: Properties,
|
|
@@ -2383,7 +2506,7 @@ var Event = z8.object({
|
|
|
2383
2506
|
* hand. Each is then admitted or refused by the runtime on its own rules
|
|
2384
2507
|
* (consent, exclusions, re-entry), which `automations.report` counts.
|
|
2385
2508
|
*/
|
|
2386
|
-
entered:
|
|
2509
|
+
entered: z9.number().int().min(0)
|
|
2387
2510
|
});
|
|
2388
2511
|
var EventListQuery = PageQuery.extend({
|
|
2389
2512
|
name: EventName.optional(),
|
|
@@ -2391,19 +2514,19 @@ var EventListQuery = PageQuery.extend({
|
|
|
2391
2514
|
});
|
|
2392
2515
|
|
|
2393
2516
|
// src/workspace.ts
|
|
2394
|
-
var CompanyId =
|
|
2517
|
+
var CompanyId = z10.string().min(1).max(64);
|
|
2395
2518
|
var ASSET_POLICIES = ["any", "service_only"];
|
|
2396
|
-
var AssetPolicy =
|
|
2519
|
+
var AssetPolicy = z10.enum(ASSET_POLICIES);
|
|
2397
2520
|
var MAX_ALLOWED_ASSET_HOSTS = 20;
|
|
2398
|
-
var AssetHost =
|
|
2521
|
+
var AssetHost = z10.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");
|
|
2399
2522
|
var MAX_MERGE_DEFAULTS = 50;
|
|
2400
2523
|
var MAX_MERGE_DEFAULT_LENGTH = 2e3;
|
|
2401
|
-
var MergeFieldName =
|
|
2402
|
-
var MergeDefaultValue =
|
|
2403
|
-
var MergeDefaults =
|
|
2524
|
+
var MergeFieldName = z10.string().regex(/^[a-z][a-z0-9_]*$/, "a merge-field name: lowercase letters, digits and underscores, starting with a letter").max(64);
|
|
2525
|
+
var MergeDefaultValue = z10.union([z10.string().max(MAX_MERGE_DEFAULT_LENGTH), z10.number().finite(), z10.boolean()]);
|
|
2526
|
+
var MergeDefaults = z10.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), {
|
|
2404
2527
|
message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
|
|
2405
2528
|
});
|
|
2406
|
-
var WorkspaceSettings =
|
|
2529
|
+
var WorkspaceSettings = z10.object({
|
|
2407
2530
|
/**
|
|
2408
2531
|
* The workspace's main language (a {@link LocaleTag}, e.g. "de"): the main
|
|
2409
2532
|
* language of a new template, and of the hosted pages when nothing else
|
|
@@ -2416,9 +2539,9 @@ var WorkspaceSettings = z9.object({
|
|
|
2416
2539
|
* each; the hosted pages are offered in those of them they support (English,
|
|
2417
2540
|
* German, French, Italian, Spanish). The service stores canonical casing.
|
|
2418
2541
|
*/
|
|
2419
|
-
locales:
|
|
2542
|
+
locales: z10.array(LocaleTag).min(1).max(20).refine((l) => new Set(l.map((t) => t.toLowerCase())).size === l.length, "a language appears twice"),
|
|
2420
2543
|
/** Open and click tracking. Off by default, opt-in per workspace. */
|
|
2421
|
-
tracking_enabled:
|
|
2544
|
+
tracking_enabled: z10.boolean(),
|
|
2422
2545
|
/** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
|
|
2423
2546
|
asset_policy: AssetPolicy,
|
|
2424
2547
|
/**
|
|
@@ -2426,7 +2549,7 @@ var WorkspaceSettings = z9.object({
|
|
|
2426
2549
|
* the workspace's website, typically. Empty by default. Ignored under `any`,
|
|
2427
2550
|
* which allows everything already.
|
|
2428
2551
|
*/
|
|
2429
|
-
allowed_asset_hosts:
|
|
2552
|
+
allowed_asset_hosts: z10.array(AssetHost).max(MAX_ALLOWED_ASSET_HOSTS),
|
|
2430
2553
|
/** Merge values every mail of the workspace can use (see `MergeDefaults`). Empty by default. */
|
|
2431
2554
|
merge_defaults: MergeDefaults,
|
|
2432
2555
|
/**
|
|
@@ -2435,10 +2558,10 @@ var WorkspaceSettings = z9.object({
|
|
|
2435
2558
|
*/
|
|
2436
2559
|
default_timezone: TimezoneName.nullable()
|
|
2437
2560
|
});
|
|
2438
|
-
var Workspace =
|
|
2561
|
+
var Workspace = z10.object({
|
|
2439
2562
|
id: Id,
|
|
2440
2563
|
slug: Slug,
|
|
2441
|
-
name:
|
|
2564
|
+
name: z10.string().min(1).max(120),
|
|
2442
2565
|
settings: WorkspaceSettings,
|
|
2443
2566
|
/**
|
|
2444
2567
|
* The auth-brain company (tenant) the workspace was created for, or null for
|
|
@@ -2449,13 +2572,13 @@ var Workspace = z9.object({
|
|
|
2449
2572
|
created_at: Timestamp,
|
|
2450
2573
|
updated_at: Timestamp
|
|
2451
2574
|
});
|
|
2452
|
-
var WorkspaceCreate =
|
|
2575
|
+
var WorkspaceCreate = z10.object({
|
|
2453
2576
|
slug: Slug,
|
|
2454
|
-
name:
|
|
2577
|
+
name: z10.string().min(1).max(120),
|
|
2455
2578
|
settings: WorkspaceSettings.partial().optional(),
|
|
2456
|
-
owner:
|
|
2579
|
+
owner: z10.object({
|
|
2457
2580
|
email: Email,
|
|
2458
|
-
name:
|
|
2581
|
+
name: z10.string().max(200).nullable().optional()
|
|
2459
2582
|
}),
|
|
2460
2583
|
/**
|
|
2461
2584
|
* The signed-in person's active auth-brain company. The dashboard always
|
|
@@ -2465,49 +2588,49 @@ var WorkspaceCreate = z9.object({
|
|
|
2465
2588
|
*/
|
|
2466
2589
|
company_id: CompanyId.optional()
|
|
2467
2590
|
});
|
|
2468
|
-
var WorkspaceUpdate =
|
|
2469
|
-
name:
|
|
2591
|
+
var WorkspaceUpdate = z10.object({
|
|
2592
|
+
name: z10.string().min(1).max(120),
|
|
2470
2593
|
settings: WorkspaceSettings.partial()
|
|
2471
2594
|
}).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
|
|
2472
|
-
var WorkspaceMove =
|
|
2595
|
+
var WorkspaceMove = z10.object({ company_id: CompanyId });
|
|
2473
2596
|
var MEMBER_ROLES = ["owner", "admin", "editor", "viewer"];
|
|
2474
|
-
var MemberRole =
|
|
2597
|
+
var MemberRole = z10.enum(MEMBER_ROLES);
|
|
2475
2598
|
var WorkspaceMembership = Workspace.extend({ role: MemberRole });
|
|
2476
|
-
var Member =
|
|
2599
|
+
var Member = z10.object({
|
|
2477
2600
|
id: Id,
|
|
2478
2601
|
/** The person's auth-brain subject. */
|
|
2479
|
-
subject:
|
|
2602
|
+
subject: z10.string().min(1).max(255),
|
|
2480
2603
|
email: Email,
|
|
2481
|
-
name:
|
|
2604
|
+
name: z10.string().max(200).nullable(),
|
|
2482
2605
|
role: MemberRole,
|
|
2483
2606
|
created_at: Timestamp
|
|
2484
2607
|
});
|
|
2485
|
-
var MemberCreate =
|
|
2486
|
-
subject:
|
|
2608
|
+
var MemberCreate = z10.object({
|
|
2609
|
+
subject: z10.string().min(1).max(255),
|
|
2487
2610
|
email: Email,
|
|
2488
|
-
name:
|
|
2611
|
+
name: z10.string().max(200).nullable().optional(),
|
|
2489
2612
|
role: MemberRole
|
|
2490
2613
|
});
|
|
2491
|
-
var MemberUpdate =
|
|
2614
|
+
var MemberUpdate = z10.object({ role: MemberRole });
|
|
2492
2615
|
var API_KEY_SCOPES = ["full", "read", "send"];
|
|
2493
|
-
var ApiKeyScope =
|
|
2494
|
-
var ApiKey =
|
|
2616
|
+
var ApiKeyScope = z10.enum(API_KEY_SCOPES);
|
|
2617
|
+
var ApiKey = z10.object({
|
|
2495
2618
|
id: Id,
|
|
2496
|
-
name:
|
|
2619
|
+
name: z10.string().min(1).max(120),
|
|
2497
2620
|
/** The first characters of the key, for recognising it in a list. */
|
|
2498
|
-
prefix:
|
|
2621
|
+
prefix: z10.string().min(4).max(32),
|
|
2499
2622
|
scope: ApiKeyScope,
|
|
2500
2623
|
last_used_at: Timestamp.nullable(),
|
|
2501
2624
|
revoked_at: Timestamp.nullable(),
|
|
2502
2625
|
created_at: Timestamp
|
|
2503
2626
|
});
|
|
2504
|
-
var ApiKeyCreate =
|
|
2505
|
-
name:
|
|
2627
|
+
var ApiKeyCreate = z10.object({
|
|
2628
|
+
name: z10.string().min(1).max(120),
|
|
2506
2629
|
/** Omitted means `full`. */
|
|
2507
2630
|
scope: ApiKeyScope.optional()
|
|
2508
2631
|
});
|
|
2509
|
-
var ApiKeyCreated =
|
|
2510
|
-
key:
|
|
2632
|
+
var ApiKeyCreated = z10.object({
|
|
2633
|
+
key: z10.string().min(20),
|
|
2511
2634
|
api_key: ApiKey
|
|
2512
2635
|
});
|
|
2513
2636
|
var AUDIT_ACTIONS = [
|
|
@@ -2534,6 +2657,7 @@ var AUDIT_ACTIONS = [
|
|
|
2534
2657
|
"template.created",
|
|
2535
2658
|
"template.updated",
|
|
2536
2659
|
"template.deleted",
|
|
2660
|
+
"template.inputs_declared",
|
|
2537
2661
|
"saved_section.created",
|
|
2538
2662
|
"saved_section.updated",
|
|
2539
2663
|
"saved_section.deleted",
|
|
@@ -2595,29 +2719,29 @@ var AUDIT_ACTIONS = [
|
|
|
2595
2719
|
"automation.run_ended",
|
|
2596
2720
|
"event.recorded"
|
|
2597
2721
|
];
|
|
2598
|
-
var AuditAction =
|
|
2599
|
-
var AuditActor =
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
type:
|
|
2722
|
+
var AuditAction = z10.enum(AUDIT_ACTIONS);
|
|
2723
|
+
var AuditActor = z10.discriminatedUnion("type", [
|
|
2724
|
+
z10.object({ type: z10.literal("member"), member_id: Id, subject: z10.string().min(1) }),
|
|
2725
|
+
z10.object({
|
|
2726
|
+
type: z10.literal("api_key"),
|
|
2603
2727
|
api_key_id: Id,
|
|
2604
2728
|
/**
|
|
2605
2729
|
* Who the client's application said it was acting for, from
|
|
2606
2730
|
* `ON_BEHALF_OF_HEADER`: reported by that application, never verified.
|
|
2607
2731
|
* Absent when the call carried no label.
|
|
2608
2732
|
*/
|
|
2609
|
-
on_behalf_of:
|
|
2733
|
+
on_behalf_of: z10.string().min(1).max(ON_BEHALF_OF_MAX_LENGTH).optional()
|
|
2610
2734
|
}),
|
|
2611
2735
|
/** The service itself (the worker, a bounce, the hosted unsubscribe page). */
|
|
2612
|
-
|
|
2736
|
+
z10.object({ type: z10.literal("system"), reason: z10.string().min(1).max(200) })
|
|
2613
2737
|
]);
|
|
2614
|
-
var AuditEntry =
|
|
2738
|
+
var AuditEntry = z10.object({
|
|
2615
2739
|
id: Id,
|
|
2616
2740
|
action: AuditAction,
|
|
2617
2741
|
actor: AuditActor,
|
|
2618
|
-
target_type:
|
|
2742
|
+
target_type: z10.string().min(1).max(64),
|
|
2619
2743
|
target_id: Id.nullable(),
|
|
2620
|
-
details:
|
|
2744
|
+
details: z10.record(z10.unknown()),
|
|
2621
2745
|
created_at: Timestamp
|
|
2622
2746
|
});
|
|
2623
2747
|
var AuditQuery = PageQuery.extend({
|
|
@@ -2626,12 +2750,12 @@ var AuditQuery = PageQuery.extend({
|
|
|
2626
2750
|
});
|
|
2627
2751
|
|
|
2628
2752
|
// src/invites.ts
|
|
2629
|
-
import { z as
|
|
2753
|
+
import { z as z11 } from "zod";
|
|
2630
2754
|
var INVITE_STATUSES = ["pending", "accepted", "revoked", "expired"];
|
|
2631
|
-
var InviteStatus =
|
|
2755
|
+
var InviteStatus = z11.enum(INVITE_STATUSES);
|
|
2632
2756
|
var DEFAULT_INVITE_TTL_DAYS = 7;
|
|
2633
2757
|
var MAX_INVITE_TTL_DAYS = 30;
|
|
2634
|
-
var Invite =
|
|
2758
|
+
var Invite = z11.object({
|
|
2635
2759
|
id: Id,
|
|
2636
2760
|
/** Stored lowercased; only a person signed in with this address can accept. */
|
|
2637
2761
|
email: Email,
|
|
@@ -2639,32 +2763,32 @@ var Invite = z10.object({
|
|
|
2639
2763
|
/** Derived from the timestamps below, never stored. */
|
|
2640
2764
|
status: InviteStatus,
|
|
2641
2765
|
/** The member who invited, while they are still one (null after they left). */
|
|
2642
|
-
invited_by:
|
|
2766
|
+
invited_by: z11.object({ member_id: Id.nullable(), email: Email.nullable() }),
|
|
2643
2767
|
expires_at: Timestamp,
|
|
2644
2768
|
accepted_at: Timestamp.nullable(),
|
|
2645
2769
|
revoked_at: Timestamp.nullable(),
|
|
2646
2770
|
created_at: Timestamp
|
|
2647
2771
|
});
|
|
2648
|
-
var InviteCreate =
|
|
2772
|
+
var InviteCreate = z11.object({
|
|
2649
2773
|
email: Email,
|
|
2650
2774
|
role: MemberRole,
|
|
2651
|
-
expires_in_days:
|
|
2775
|
+
expires_in_days: z11.number().int().min(1).max(MAX_INVITE_TTL_DAYS).optional()
|
|
2652
2776
|
});
|
|
2653
|
-
var InviteCreated =
|
|
2777
|
+
var InviteCreated = z11.object({
|
|
2654
2778
|
invite: Invite,
|
|
2655
|
-
token:
|
|
2779
|
+
token: z11.string().min(32).max(128)
|
|
2656
2780
|
});
|
|
2657
2781
|
var InviteListQuery = PageQuery.extend({
|
|
2658
2782
|
status: InviteStatus.optional()
|
|
2659
2783
|
});
|
|
2660
|
-
var InviteAccept =
|
|
2661
|
-
token:
|
|
2784
|
+
var InviteAccept = z11.object({
|
|
2785
|
+
token: z11.string().min(32).max(128),
|
|
2662
2786
|
email: Email,
|
|
2663
|
-
name:
|
|
2787
|
+
name: z11.string().max(200).nullable().optional()
|
|
2664
2788
|
});
|
|
2665
|
-
var InviteAccepted =
|
|
2789
|
+
var InviteAccepted = z11.object({
|
|
2666
2790
|
workspace: WorkspaceMembership,
|
|
2667
|
-
already_member:
|
|
2791
|
+
already_member: z11.boolean()
|
|
2668
2792
|
});
|
|
2669
2793
|
var INVITE_REFUSAL_REASONS = ["email_mismatch", "inviter_lacks_role", "expired", "revoked", "accepted"];
|
|
2670
2794
|
|
|
@@ -2971,14 +3095,14 @@ function insertionNameProblem(insertions, name, current) {
|
|
|
2971
3095
|
if (has(RESERVED_MERGE_FIELDS, name)) return `"${name}" is a merge field the service fills itself. Choose another name.`;
|
|
2972
3096
|
if (name !== current && has(insertions, name)) return `There is already an insertion called "${name}".`;
|
|
2973
3097
|
const chooser = Object.entries(insertions).find(([other, def]) => def.by === name && other !== current);
|
|
2974
|
-
if (chooser) return `"${name}" is the value
|
|
2975
|
-
if (current !== void 0 && ownEntry(insertions, current)?.by === name) return `"${name}" is the value
|
|
3098
|
+
if (chooser) return `"${name}" is the value "${chooser[0]}" depends on. Choose another name.`;
|
|
3099
|
+
if (current !== void 0 && ownEntry(insertions, current)?.by === name) return `"${name}" is the value this insertion depends on. Choose another name.`;
|
|
2976
3100
|
return null;
|
|
2977
3101
|
}
|
|
2978
3102
|
function insertionByProblem(insertions, by, self) {
|
|
2979
|
-
if (!InsertionName.safeParse(by).success) return "The value
|
|
2980
|
-
if (by === "unsubscribe_url") return "
|
|
2981
|
-
if (by === self || has(insertions, by)) return `"${by}" is an insertion, not a value. Use a value the
|
|
3103
|
+
if (!InsertionName.safeParse(by).success) return "The value it depends on is a merge-field name: lowercase letters, digits and underscores (like reason).";
|
|
3104
|
+
if (by === "unsubscribe_url") return "An insertion cannot depend on the unsubscribe link. Use a value the app or the contact provides.";
|
|
3105
|
+
if (by === self || has(insertions, by)) return `"${by}" is an insertion, not a value. Use a value the app or the contact provides.`;
|
|
2982
3106
|
return null;
|
|
2983
3107
|
}
|
|
2984
3108
|
function normalizeChoiceKey(raw) {
|
|
@@ -2986,10 +3110,10 @@ function normalizeChoiceKey(raw) {
|
|
|
2986
3110
|
}
|
|
2987
3111
|
function choiceKeyProblem(insertion, key, current) {
|
|
2988
3112
|
if (INSERTION_TEXT_SLOTS.includes(key)) {
|
|
2989
|
-
return `"${key}"
|
|
3113
|
+
return `"${key}" is kept for the insertion's ${key === "filled" ? '"any other value"' : '"empty"'} sentence, so it cannot be a value. Choose another value.`;
|
|
2990
3114
|
}
|
|
2991
|
-
if (!InsertionChoiceKey.safeParse(key).success) return "A
|
|
2992
|
-
if (key !== current && has(insertion.choices, key)) return `
|
|
3115
|
+
if (!InsertionChoiceKey.safeParse(key).success) return "A value is written exactly as the app sends it: lowercase letters, digits, hyphens and underscores (like venue).";
|
|
3116
|
+
if (key !== current && has(insertion.choices, key)) return `"${key}" already has its own sentence.`;
|
|
2993
3117
|
return null;
|
|
2994
3118
|
}
|
|
2995
3119
|
function addInsertion(drafts, rawName, rawBy) {
|
|
@@ -2998,7 +3122,7 @@ function addInsertion(drafts, rawName, rawBy) {
|
|
|
2998
3122
|
if (Object.keys(drafts.insertions).length >= MAX_INSERTIONS) return { ok: false, message: `A template holds at most ${MAX_INSERTIONS} insertions.` };
|
|
2999
3123
|
const nameProblem = insertionNameProblem(drafts.insertions, name);
|
|
3000
3124
|
if (nameProblem) return { ok: false, message: nameProblem };
|
|
3001
|
-
if (name === by) return { ok: false, message: "An insertion cannot
|
|
3125
|
+
if (name === by) return { ok: false, message: "An insertion cannot depend on its own name. Use the value the app sends (like reason)." };
|
|
3002
3126
|
const byProblem = insertionByProblem(drafts.insertions, by);
|
|
3003
3127
|
if (byProblem) return { ok: false, message: byProblem };
|
|
3004
3128
|
return { ok: true, drafts: editMainInsertions(drafts, { ...drafts.insertions, [name]: { by, choices: {}, filled: null, otherwise: "" } }) };
|
|
@@ -3181,6 +3305,25 @@ var REFUSAL_HINTS = {
|
|
|
3181
3305
|
insertion_with_fallback: 'Remove the |fallback from the insertion in the subject, preheader or body: its "otherwise" text is the fallback.',
|
|
3182
3306
|
unknown_translation_insertion: "A language names an insertion or a choice the main language no longer has. Reload the template and try again."
|
|
3183
3307
|
};
|
|
3308
|
+
function unknownRefusedInsertions(details, drafts) {
|
|
3309
|
+
const reason = details?.reason;
|
|
3310
|
+
if (typeof reason !== "string" || !has(REFUSAL_HINTS, reason)) return [];
|
|
3311
|
+
const names = [];
|
|
3312
|
+
const add = (name) => {
|
|
3313
|
+
if (typeof name === "string" && name !== "" && ownEntry(drafts.insertions, name) === void 0 && !names.includes(name)) names.push(name);
|
|
3314
|
+
};
|
|
3315
|
+
if (Array.isArray(details?.missing)) {
|
|
3316
|
+
for (const m of details.missing) if (typeof m === "object" && m !== null) add(m.insertion);
|
|
3317
|
+
}
|
|
3318
|
+
if (Array.isArray(details?.issues)) {
|
|
3319
|
+
for (const issue of details.issues) {
|
|
3320
|
+
const path = typeof issue === "object" && issue !== null && Array.isArray(issue.path) ? issue.path.map(String) : [];
|
|
3321
|
+
const at = path.indexOf("insertions");
|
|
3322
|
+
if (at >= 0) add(path[at + 1]);
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
return names;
|
|
3326
|
+
}
|
|
3184
3327
|
function insertionRefusalHint(details) {
|
|
3185
3328
|
const reason = details?.reason;
|
|
3186
3329
|
if (typeof reason !== "string" || !has(REFUSAL_HINTS, reason)) return null;
|
|
@@ -3189,15 +3332,99 @@ function insertionRefusalHint(details) {
|
|
|
3189
3332
|
const missing = Array.isArray(details?.missing) ? details.missing.filter((m) => typeof m === "object" && m !== null && typeof m.insertion === "string" && typeof m.choice === "string") : [];
|
|
3190
3333
|
return missing.length > 0 ? `The language has ${describeMissingTexts(missing)}. ${hint}` : hint;
|
|
3191
3334
|
}
|
|
3335
|
+
function tokenOf(name) {
|
|
3336
|
+
return new RegExp(String.raw`\{\{(\s*)${name}(\s*)(\|[^}]*)?\}\}`, "g");
|
|
3337
|
+
}
|
|
3338
|
+
function renameTokenInText(text, from, to) {
|
|
3339
|
+
let count = 0;
|
|
3340
|
+
const out = text.replace(tokenOf(from), (_m, before, after, fallback) => {
|
|
3341
|
+
count++;
|
|
3342
|
+
return `{{${before}${to}${after}${fallback ?? ""}}}`;
|
|
3343
|
+
});
|
|
3344
|
+
return { text: count > 0 ? out : text, count };
|
|
3345
|
+
}
|
|
3346
|
+
function renameTokenInDocument(document, from, to) {
|
|
3347
|
+
let count = 0;
|
|
3348
|
+
const walk = (value) => {
|
|
3349
|
+
if (typeof value === "string") {
|
|
3350
|
+
const r = renameTokenInText(value, from, to);
|
|
3351
|
+
count += r.count;
|
|
3352
|
+
return r.text;
|
|
3353
|
+
}
|
|
3354
|
+
if (Array.isArray(value)) {
|
|
3355
|
+
let changed = false;
|
|
3356
|
+
const next2 = value.map((v) => {
|
|
3357
|
+
const w = walk(v);
|
|
3358
|
+
if (w !== v) changed = true;
|
|
3359
|
+
return w;
|
|
3360
|
+
});
|
|
3361
|
+
return changed ? next2 : value;
|
|
3362
|
+
}
|
|
3363
|
+
if (value && typeof value === "object") {
|
|
3364
|
+
let changed = false;
|
|
3365
|
+
const next2 = {};
|
|
3366
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3367
|
+
const w = walk(v);
|
|
3368
|
+
if (w !== v) changed = true;
|
|
3369
|
+
next2[k] = w;
|
|
3370
|
+
}
|
|
3371
|
+
return changed ? next2 : value;
|
|
3372
|
+
}
|
|
3373
|
+
return value;
|
|
3374
|
+
};
|
|
3375
|
+
const renamed = walk(document);
|
|
3376
|
+
return { document: renamed, count };
|
|
3377
|
+
}
|
|
3378
|
+
function renameInsertionPlacements(drafts, from, to) {
|
|
3379
|
+
if (from === to) return { drafts, places: [] };
|
|
3380
|
+
const places = [];
|
|
3381
|
+
const note2 = (locale, where, count) => {
|
|
3382
|
+
if (count > 0) places.push({ locale, where, count });
|
|
3383
|
+
};
|
|
3384
|
+
const body = renameTokenInDocument(drafts.main.document, from, to);
|
|
3385
|
+
const subject = renameTokenInText(drafts.main.subject, from, to);
|
|
3386
|
+
const preheader = renameTokenInText(drafts.main.preheader, from, to);
|
|
3387
|
+
note2(null, "body", body.count);
|
|
3388
|
+
note2(null, "subject", subject.count);
|
|
3389
|
+
note2(null, "preheader", preheader.count);
|
|
3390
|
+
let out = {
|
|
3391
|
+
...drafts,
|
|
3392
|
+
main: { subject: subject.text, preheader: preheader.text, document: body.document },
|
|
3393
|
+
mainDocumentChanged: drafts.mainDocumentChanged || body.count > 0
|
|
3394
|
+
};
|
|
3395
|
+
for (const [tag, version] of Object.entries(drafts.translations)) {
|
|
3396
|
+
const b = renameTokenInDocument(version.document, from, to);
|
|
3397
|
+
const s = version.subject === null ? { text: null, count: 0 } : renameTokenInText(version.subject, from, to);
|
|
3398
|
+
const p = version.preheader === null ? { text: null, count: 0 } : renameTokenInText(version.preheader, from, to);
|
|
3399
|
+
note2(tag, "body", b.count);
|
|
3400
|
+
note2(tag, "subject", s.count);
|
|
3401
|
+
note2(tag, "preheader", p.count);
|
|
3402
|
+
if (b.count + s.count + p.count === 0) continue;
|
|
3403
|
+
out = {
|
|
3404
|
+
...out,
|
|
3405
|
+
translations: { ...out.translations, [tag]: { ...version, document: b.document, subject: s.text, preheader: p.text } },
|
|
3406
|
+
changed: out.changed.includes(tag) ? out.changed : [...out.changed, tag]
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3409
|
+
return { drafts: out, places };
|
|
3410
|
+
}
|
|
3411
|
+
function renameInsertionEverywhere(drafts, from, rawTo) {
|
|
3412
|
+
const renamed = renameInsertionChecked(drafts, from, rawTo);
|
|
3413
|
+
if (!renamed.ok) return renamed;
|
|
3414
|
+
const to = rawTo.trim();
|
|
3415
|
+
if (to === from) return { ok: true, drafts, places: [] };
|
|
3416
|
+
const placed = renameInsertionPlacements(renamed.drafts, from, to);
|
|
3417
|
+
return { ok: true, drafts: placed.drafts, places: placed.places };
|
|
3418
|
+
}
|
|
3192
3419
|
|
|
3193
3420
|
// src/providers.ts
|
|
3194
|
-
import { z as
|
|
3421
|
+
import { z as z12 } from "zod";
|
|
3195
3422
|
var PROVIDER_KINDS = ["smtp", "resend"];
|
|
3196
|
-
var ProviderKind =
|
|
3197
|
-
var ProviderPolicy =
|
|
3198
|
-
daily_recipient_budget:
|
|
3199
|
-
min_interval_ms:
|
|
3200
|
-
max_recipients_per_message:
|
|
3423
|
+
var ProviderKind = z12.enum(PROVIDER_KINDS);
|
|
3424
|
+
var ProviderPolicy = z12.object({
|
|
3425
|
+
daily_recipient_budget: z12.number().int().min(1).max(1e7),
|
|
3426
|
+
min_interval_ms: z12.number().int().min(0).max(36e5),
|
|
3427
|
+
max_recipients_per_message: z12.number().int().min(1).max(1e3)
|
|
3201
3428
|
});
|
|
3202
3429
|
var ICLOUD_SMTP_POLICY = {
|
|
3203
3430
|
daily_recipient_budget: 800,
|
|
@@ -3205,17 +3432,17 @@ var ICLOUD_SMTP_POLICY = {
|
|
|
3205
3432
|
max_recipients_per_message: 1
|
|
3206
3433
|
};
|
|
3207
3434
|
var SMTP_SECURITY = ["tls", "starttls"];
|
|
3208
|
-
var SmtpConfigBase =
|
|
3209
|
-
host:
|
|
3210
|
-
port:
|
|
3435
|
+
var SmtpConfigBase = z12.object({
|
|
3436
|
+
host: z12.string().min(1).max(255),
|
|
3437
|
+
port: z12.number().int().min(1).max(65535),
|
|
3211
3438
|
/** `tls` connects encrypted (465), `starttls` upgrades (587). Never plaintext. */
|
|
3212
|
-
security:
|
|
3213
|
-
username:
|
|
3439
|
+
security: z12.enum(SMTP_SECURITY),
|
|
3440
|
+
username: z12.string().min(1).max(255)
|
|
3214
3441
|
});
|
|
3215
|
-
var ResendConfigBase =
|
|
3216
|
-
var ProviderRejections =
|
|
3217
|
-
count:
|
|
3218
|
-
last_error:
|
|
3442
|
+
var ResendConfigBase = z12.object({});
|
|
3443
|
+
var ProviderRejections = z12.object({
|
|
3444
|
+
count: z12.number().int().min(0),
|
|
3445
|
+
last_error: z12.string().nullable(),
|
|
3219
3446
|
last_at: Timestamp.nullable(),
|
|
3220
3447
|
/**
|
|
3221
3448
|
* The last time the bounce circuit breaker tripped on one of this provider's
|
|
@@ -3225,59 +3452,59 @@ var ProviderRejections = z11.object({
|
|
|
3225
3452
|
* run's bounce blocks were undone and the mailing paused. `sample` is the
|
|
3226
3453
|
* reply that tripped it.
|
|
3227
3454
|
*/
|
|
3228
|
-
anomaly:
|
|
3455
|
+
anomaly: z12.object({
|
|
3229
3456
|
at: Timestamp,
|
|
3230
3457
|
/** `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. */
|
|
3231
|
-
scope:
|
|
3458
|
+
scope: z12.enum(["mailing", "provider"]),
|
|
3232
3459
|
/**
|
|
3233
3460
|
* True while the provider-wide breaker is open: no bounce blocks through
|
|
3234
3461
|
* this provider, test sends refused and mailings not started or resumed
|
|
3235
3462
|
* (`provider_anomaly`), until an admin clears it (`providers.clearAnomaly`).
|
|
3236
3463
|
*/
|
|
3237
|
-
blocking:
|
|
3464
|
+
blocking: z12.boolean(),
|
|
3238
3465
|
mailing_id: Id.nullable(),
|
|
3239
|
-
reason:
|
|
3240
|
-
sample:
|
|
3466
|
+
reason: z12.string(),
|
|
3467
|
+
sample: z12.string()
|
|
3241
3468
|
}).nullable()
|
|
3242
3469
|
});
|
|
3243
|
-
var ProviderEvents =
|
|
3244
|
-
status:
|
|
3245
|
-
source:
|
|
3246
|
-
url:
|
|
3247
|
-
error:
|
|
3470
|
+
var ProviderEvents = z12.object({
|
|
3471
|
+
status: z12.enum(["active", "needs_secret"]),
|
|
3472
|
+
source: z12.enum(["automatic", "manual"]).nullable(),
|
|
3473
|
+
url: z12.string().url(),
|
|
3474
|
+
error: z12.string().nullable(),
|
|
3248
3475
|
/**
|
|
3249
3476
|
* Events that named an email this provider never sent through the service
|
|
3250
3477
|
* (another workspace or system sharing the Resend account): counted, never
|
|
3251
3478
|
* acted on.
|
|
3252
3479
|
*/
|
|
3253
|
-
unmatched:
|
|
3480
|
+
unmatched: z12.number().int().min(0)
|
|
3254
3481
|
});
|
|
3255
3482
|
var RESEND_EVENT_TYPES = ["email.bounced", "email.complained", "email.delivery_delayed"];
|
|
3256
|
-
var ResendSigningSecret =
|
|
3257
|
-
var ProviderEventsSecret =
|
|
3483
|
+
var ResendSigningSecret = z12.string().max(200).regex(/^whsec_[A-Za-z0-9+/]{16,}={0,2}$/, "must be the signing secret Resend shows, starting with whsec_");
|
|
3484
|
+
var ProviderEventsSecret = z12.object({ signing_secret: ResendSigningSecret });
|
|
3258
3485
|
var ProviderFields = {
|
|
3259
|
-
name:
|
|
3260
|
-
from_name:
|
|
3486
|
+
name: z12.string().min(1).max(120),
|
|
3487
|
+
from_name: z12.string().min(1).max(120),
|
|
3261
3488
|
from_email: Email,
|
|
3262
3489
|
reply_to: Email.nullable(),
|
|
3263
3490
|
policy: ProviderPolicy
|
|
3264
3491
|
};
|
|
3265
|
-
var Provider =
|
|
3266
|
-
|
|
3492
|
+
var Provider = z12.discriminatedUnion("kind", [
|
|
3493
|
+
z12.object({
|
|
3267
3494
|
id: Id,
|
|
3268
|
-
kind:
|
|
3495
|
+
kind: z12.literal("smtp"),
|
|
3269
3496
|
config: SmtpConfigBase,
|
|
3270
|
-
has_secret:
|
|
3497
|
+
has_secret: z12.boolean(),
|
|
3271
3498
|
...ProviderFields,
|
|
3272
3499
|
rejections: ProviderRejections,
|
|
3273
3500
|
created_at: Timestamp,
|
|
3274
3501
|
updated_at: Timestamp
|
|
3275
3502
|
}),
|
|
3276
|
-
|
|
3503
|
+
z12.object({
|
|
3277
3504
|
id: Id,
|
|
3278
|
-
kind:
|
|
3505
|
+
kind: z12.literal("resend"),
|
|
3279
3506
|
config: ResendConfigBase,
|
|
3280
|
-
has_secret:
|
|
3507
|
+
has_secret: z12.boolean(),
|
|
3281
3508
|
...ProviderFields,
|
|
3282
3509
|
rejections: ProviderRejections,
|
|
3283
3510
|
events: ProviderEvents,
|
|
@@ -3285,31 +3512,31 @@ var Provider = z11.discriminatedUnion("kind", [
|
|
|
3285
3512
|
updated_at: Timestamp
|
|
3286
3513
|
})
|
|
3287
3514
|
]);
|
|
3288
|
-
var ProviderCreate =
|
|
3289
|
-
|
|
3290
|
-
kind:
|
|
3291
|
-
config: SmtpConfigBase.extend({ password:
|
|
3515
|
+
var ProviderCreate = z12.discriminatedUnion("kind", [
|
|
3516
|
+
z12.object({
|
|
3517
|
+
kind: z12.literal("smtp"),
|
|
3518
|
+
config: SmtpConfigBase.extend({ password: z12.string().min(1).max(1024) }),
|
|
3292
3519
|
...ProviderFields
|
|
3293
3520
|
}),
|
|
3294
|
-
|
|
3295
|
-
kind:
|
|
3296
|
-
config: ResendConfigBase.extend({ api_key:
|
|
3521
|
+
z12.object({
|
|
3522
|
+
kind: z12.literal("resend"),
|
|
3523
|
+
config: ResendConfigBase.extend({ api_key: z12.string().min(1).max(1024) }),
|
|
3297
3524
|
...ProviderFields
|
|
3298
3525
|
})
|
|
3299
3526
|
]);
|
|
3300
|
-
var ProviderUpdate =
|
|
3301
|
-
|
|
3302
|
-
kind:
|
|
3303
|
-
config: SmtpConfigBase.extend({ password:
|
|
3527
|
+
var ProviderUpdate = z12.discriminatedUnion("kind", [
|
|
3528
|
+
z12.object({
|
|
3529
|
+
kind: z12.literal("smtp"),
|
|
3530
|
+
config: SmtpConfigBase.extend({ password: z12.string().min(1).max(1024).optional() }).partial().optional(),
|
|
3304
3531
|
name: ProviderFields.name.optional(),
|
|
3305
3532
|
from_name: ProviderFields.from_name.optional(),
|
|
3306
3533
|
from_email: ProviderFields.from_email.optional(),
|
|
3307
3534
|
reply_to: ProviderFields.reply_to.optional(),
|
|
3308
3535
|
policy: ProviderPolicy.partial().optional()
|
|
3309
3536
|
}),
|
|
3310
|
-
|
|
3311
|
-
kind:
|
|
3312
|
-
config: ResendConfigBase.extend({ api_key:
|
|
3537
|
+
z12.object({
|
|
3538
|
+
kind: z12.literal("resend"),
|
|
3539
|
+
config: ResendConfigBase.extend({ api_key: z12.string().min(1).max(1024).optional() }).optional(),
|
|
3313
3540
|
name: ProviderFields.name.optional(),
|
|
3314
3541
|
from_name: ProviderFields.from_name.optional(),
|
|
3315
3542
|
from_email: ProviderFields.from_email.optional(),
|
|
@@ -3317,48 +3544,48 @@ var ProviderUpdate = z11.discriminatedUnion("kind", [
|
|
|
3317
3544
|
policy: ProviderPolicy.partial().optional()
|
|
3318
3545
|
})
|
|
3319
3546
|
]);
|
|
3320
|
-
var ProviderUsage =
|
|
3547
|
+
var ProviderUsage = z12.object({
|
|
3321
3548
|
provider_id: Id,
|
|
3322
|
-
recipients_last_24h:
|
|
3323
|
-
remaining_budget:
|
|
3549
|
+
recipients_last_24h: z12.number().int().min(0),
|
|
3550
|
+
remaining_budget: z12.number().int().min(0),
|
|
3324
3551
|
/** When enough of the rolling window frees up to send again, if exhausted. */
|
|
3325
3552
|
next_capacity_at: Timestamp.nullable()
|
|
3326
3553
|
});
|
|
3327
|
-
var ProviderVerifyResult =
|
|
3328
|
-
ok:
|
|
3329
|
-
error:
|
|
3554
|
+
var ProviderVerifyResult = z12.object({
|
|
3555
|
+
ok: z12.boolean(),
|
|
3556
|
+
error: z12.string().nullable()
|
|
3330
3557
|
});
|
|
3331
3558
|
|
|
3332
3559
|
// src/contacts.ts
|
|
3333
|
-
import { z as
|
|
3334
|
-
var Topic =
|
|
3560
|
+
import { z as z13 } from "zod";
|
|
3561
|
+
var Topic = z13.object({
|
|
3335
3562
|
id: Id,
|
|
3336
3563
|
slug: Slug,
|
|
3337
|
-
name:
|
|
3564
|
+
name: z13.string().min(1).max(120),
|
|
3338
3565
|
/** Shown on the hosted preference page. */
|
|
3339
|
-
description:
|
|
3566
|
+
description: z13.string().max(1e3).nullable(),
|
|
3340
3567
|
/** Translations of `name` and `description` per locale, for the hosted page. */
|
|
3341
|
-
translations:
|
|
3342
|
-
|
|
3568
|
+
translations: z13.record(
|
|
3569
|
+
z13.object({ name: z13.string().min(1).max(120), description: z13.string().max(1e3).nullable() })
|
|
3343
3570
|
),
|
|
3344
3571
|
created_at: Timestamp,
|
|
3345
3572
|
updated_at: Timestamp
|
|
3346
3573
|
});
|
|
3347
|
-
var TopicCreate =
|
|
3574
|
+
var TopicCreate = z13.object({
|
|
3348
3575
|
slug: Slug,
|
|
3349
|
-
name:
|
|
3350
|
-
description:
|
|
3576
|
+
name: z13.string().min(1).max(120),
|
|
3577
|
+
description: z13.string().max(1e3).optional(),
|
|
3351
3578
|
translations: Topic.shape.translations.optional()
|
|
3352
3579
|
});
|
|
3353
3580
|
var TopicUpdate = TopicCreate.omit({ slug: true }).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
|
|
3354
|
-
var Contact =
|
|
3581
|
+
var Contact = z13.object({
|
|
3355
3582
|
id: Id,
|
|
3356
|
-
external_id:
|
|
3583
|
+
external_id: z13.string().min(1).max(255).nullable(),
|
|
3357
3584
|
/** Stored lowercased, unique per workspace. */
|
|
3358
3585
|
email: Email,
|
|
3359
|
-
first_name:
|
|
3360
|
-
last_name:
|
|
3361
|
-
locale:
|
|
3586
|
+
first_name: z13.string().max(200).nullable(),
|
|
3587
|
+
last_name: z13.string().max(200).nullable(),
|
|
3588
|
+
locale: z13.string().min(2).max(35).nullable(),
|
|
3362
3589
|
/**
|
|
3363
3590
|
* The person's own time zone, an IANA name such as `Europe/Berlin` (A1).
|
|
3364
3591
|
* Every wall clock an automation waits for is read in it, falling back to the
|
|
@@ -3366,65 +3593,65 @@ var Contact = z12.object({
|
|
|
3366
3593
|
*/
|
|
3367
3594
|
timezone: TimezoneName.nullable(),
|
|
3368
3595
|
properties: Properties,
|
|
3369
|
-
topics:
|
|
3596
|
+
topics: z13.array(Slug),
|
|
3370
3597
|
/** The slugs of the contact's tags (S4). */
|
|
3371
|
-
tags:
|
|
3598
|
+
tags: z13.array(Slug),
|
|
3372
3599
|
created_at: Timestamp,
|
|
3373
3600
|
updated_at: Timestamp
|
|
3374
3601
|
});
|
|
3375
|
-
var ContactUpsert =
|
|
3376
|
-
external_id:
|
|
3602
|
+
var ContactUpsert = z13.object({
|
|
3603
|
+
external_id: z13.string().min(1).max(255).optional(),
|
|
3377
3604
|
email: Email.optional(),
|
|
3378
|
-
first_name:
|
|
3379
|
-
last_name:
|
|
3380
|
-
locale:
|
|
3605
|
+
first_name: z13.string().max(200).nullable().optional(),
|
|
3606
|
+
last_name: z13.string().max(200).nullable().optional(),
|
|
3607
|
+
locale: z13.string().min(2).max(35).nullable().optional(),
|
|
3381
3608
|
timezone: TimezoneName.nullable().optional(),
|
|
3382
3609
|
properties: Properties.optional(),
|
|
3383
|
-
topics:
|
|
3610
|
+
topics: z13.array(Slug).max(100).optional()
|
|
3384
3611
|
}).refine((v) => v.external_id !== void 0 || v.email !== void 0, {
|
|
3385
3612
|
message: "external_id or email is required",
|
|
3386
3613
|
path: ["email"]
|
|
3387
3614
|
});
|
|
3388
|
-
var ContactUpsertResult =
|
|
3615
|
+
var ContactUpsertResult = z13.object({
|
|
3389
3616
|
contact: Contact,
|
|
3390
|
-
created:
|
|
3617
|
+
created: z13.boolean()
|
|
3391
3618
|
});
|
|
3392
3619
|
var ContactListQuery = PageQuery.extend({
|
|
3393
|
-
email:
|
|
3394
|
-
external_id:
|
|
3620
|
+
email: z13.string().min(1).max(254).optional(),
|
|
3621
|
+
external_id: z13.string().min(1).max(255).optional(),
|
|
3395
3622
|
topic: Slug.optional()
|
|
3396
3623
|
});
|
|
3397
|
-
var ContactErased =
|
|
3398
|
-
ok:
|
|
3399
|
-
erased_messages:
|
|
3400
|
-
erased_recipients:
|
|
3401
|
-
suppressions_kept:
|
|
3624
|
+
var ContactErased = z13.object({
|
|
3625
|
+
ok: z13.literal(true),
|
|
3626
|
+
erased_messages: z13.number().int().min(0),
|
|
3627
|
+
erased_recipients: z13.number().int().min(0),
|
|
3628
|
+
suppressions_kept: z13.number().int().min(0)
|
|
3402
3629
|
});
|
|
3403
3630
|
var SUPPRESSION_REASONS = ["unsubscribed", "bounced", "complained", "manual"];
|
|
3404
|
-
var SuppressionReason =
|
|
3405
|
-
var Suppression =
|
|
3631
|
+
var SuppressionReason = z13.enum(SUPPRESSION_REASONS);
|
|
3632
|
+
var Suppression = z13.object({
|
|
3406
3633
|
id: Id,
|
|
3407
3634
|
email: Email,
|
|
3408
3635
|
reason: SuppressionReason,
|
|
3409
3636
|
topic: Slug.nullable(),
|
|
3410
3637
|
source_message_id: Id.nullable(),
|
|
3411
|
-
note:
|
|
3638
|
+
note: z13.string().max(1e3).nullable(),
|
|
3412
3639
|
created_at: Timestamp
|
|
3413
3640
|
});
|
|
3414
|
-
var SuppressionCreate =
|
|
3641
|
+
var SuppressionCreate = z13.object({
|
|
3415
3642
|
email: Email,
|
|
3416
|
-
reason:
|
|
3643
|
+
reason: z13.enum(["manual", "unsubscribed"]),
|
|
3417
3644
|
topic: Slug.nullable().optional(),
|
|
3418
|
-
note:
|
|
3645
|
+
note: z13.string().max(1e3).optional()
|
|
3419
3646
|
});
|
|
3420
3647
|
var SuppressionListQuery = PageQuery.extend({
|
|
3421
|
-
email:
|
|
3648
|
+
email: z13.string().min(1).max(254).optional(),
|
|
3422
3649
|
reason: SuppressionReason.optional(),
|
|
3423
3650
|
topic: Slug.optional()
|
|
3424
3651
|
});
|
|
3425
3652
|
|
|
3426
3653
|
// src/webhooks.ts
|
|
3427
|
-
import { z as
|
|
3654
|
+
import { z as z14 } from "zod";
|
|
3428
3655
|
var WEBHOOK_EVENT_TYPES = [
|
|
3429
3656
|
"message.sent",
|
|
3430
3657
|
"message.failed",
|
|
@@ -3468,23 +3695,23 @@ var WEBHOOK_EVENT_TYPES = [
|
|
|
3468
3695
|
"template.updated",
|
|
3469
3696
|
"template.deleted"
|
|
3470
3697
|
];
|
|
3471
|
-
var WebhookEventType =
|
|
3472
|
-
var MessageEventBase =
|
|
3698
|
+
var WebhookEventType = z14.enum(WEBHOOK_EVENT_TYPES);
|
|
3699
|
+
var MessageEventBase = z14.object({
|
|
3473
3700
|
message_id: Id,
|
|
3474
3701
|
mailing_id: Id.nullable(),
|
|
3475
3702
|
mailing_metadata: MailingMetadata.nullable(),
|
|
3476
3703
|
recipient_id: Id.nullable(),
|
|
3477
3704
|
contact_id: Id.nullable(),
|
|
3478
|
-
external_id:
|
|
3705
|
+
external_id: z14.string().nullable(),
|
|
3479
3706
|
email: Email,
|
|
3480
|
-
subject:
|
|
3707
|
+
subject: z14.string(),
|
|
3481
3708
|
topic: Slug.nullable(),
|
|
3482
|
-
provider_message_id:
|
|
3483
|
-
is_test:
|
|
3709
|
+
provider_message_id: z14.string().nullable(),
|
|
3710
|
+
is_test: z14.boolean()
|
|
3484
3711
|
});
|
|
3485
3712
|
var MessageSentData = MessageEventBase.extend({
|
|
3486
3713
|
/** The final HTML as sent, so the client can archive it beside its own records. */
|
|
3487
|
-
html:
|
|
3714
|
+
html: z14.string(),
|
|
3488
3715
|
sent_at: Timestamp,
|
|
3489
3716
|
/**
|
|
3490
3717
|
* The choice each insertion made (see `Message.insertions`); absent or null
|
|
@@ -3495,20 +3722,20 @@ var MessageSentData = MessageEventBase.extend({
|
|
|
3495
3722
|
insertions: InsertionChoices.nullish()
|
|
3496
3723
|
});
|
|
3497
3724
|
var MessageFailedData = MessageEventBase.extend({
|
|
3498
|
-
error:
|
|
3725
|
+
error: z14.string(),
|
|
3499
3726
|
/** False when the provider rejected permanently; the recipient will not be retried. */
|
|
3500
|
-
retryable:
|
|
3727
|
+
retryable: z14.boolean(),
|
|
3501
3728
|
failed_at: Timestamp
|
|
3502
3729
|
});
|
|
3503
3730
|
var UNSUBSCRIBE_SOURCES = ["hosted_page", "one_click", "api", "dashboard", "signup_form"];
|
|
3504
|
-
var ContactUnsubscribedData =
|
|
3731
|
+
var ContactUnsubscribedData = z14.object({
|
|
3505
3732
|
contact_id: Id.nullable(),
|
|
3506
|
-
external_id:
|
|
3733
|
+
external_id: z14.string().nullable(),
|
|
3507
3734
|
email: Email,
|
|
3508
3735
|
/** The topic unsubscribed from; null means every topic. */
|
|
3509
3736
|
topic: Slug.nullable(),
|
|
3510
3737
|
mailing_id: Id.nullable(),
|
|
3511
|
-
source:
|
|
3738
|
+
source: z14.enum(UNSUBSCRIBE_SOURCES),
|
|
3512
3739
|
unsubscribed_at: Timestamp
|
|
3513
3740
|
});
|
|
3514
3741
|
var ContactResubscribedData = ContactUnsubscribedData.omit({ unsubscribed_at: true }).extend({
|
|
@@ -3517,142 +3744,142 @@ var ContactResubscribedData = ContactUnsubscribedData.omit({ unsubscribed_at: tr
|
|
|
3517
3744
|
* breaker undid a `bounced` block it judged false (a `contact.bounced` for the
|
|
3518
3745
|
* same address and mailing went out before); `topic` is null.
|
|
3519
3746
|
*/
|
|
3520
|
-
source:
|
|
3747
|
+
source: z14.enum([...UNSUBSCRIBE_SOURCES, "bounce_reverted"]),
|
|
3521
3748
|
resubscribed_at: Timestamp
|
|
3522
3749
|
});
|
|
3523
|
-
var ContactBouncedData =
|
|
3750
|
+
var ContactBouncedData = z14.object({
|
|
3524
3751
|
contact_id: Id.nullable(),
|
|
3525
|
-
external_id:
|
|
3752
|
+
external_id: z14.string().nullable(),
|
|
3526
3753
|
email: Email,
|
|
3527
3754
|
/** `bounced` for a hard bounce, `complained` for a spam complaint. */
|
|
3528
|
-
reason:
|
|
3755
|
+
reason: z14.enum(["bounced", "complained"]),
|
|
3529
3756
|
message_id: Id.nullable(),
|
|
3530
|
-
diagnostic:
|
|
3757
|
+
diagnostic: z14.string().nullable(),
|
|
3531
3758
|
bounced_at: Timestamp
|
|
3532
3759
|
});
|
|
3533
|
-
var MailingFinishedData =
|
|
3760
|
+
var MailingFinishedData = z14.object({
|
|
3534
3761
|
mailing_id: Id,
|
|
3535
3762
|
mailing_metadata: MailingMetadata,
|
|
3536
|
-
status:
|
|
3763
|
+
status: z14.enum(["sent", "partially_failed", "cancelled"]),
|
|
3537
3764
|
counts: MailingCounts,
|
|
3538
3765
|
finished_at: Timestamp
|
|
3539
3766
|
});
|
|
3540
|
-
var ContactSubscribedData =
|
|
3767
|
+
var ContactSubscribedData = z14.object({
|
|
3541
3768
|
contact_id: Id,
|
|
3542
|
-
external_id:
|
|
3769
|
+
external_id: z14.string().nullable(),
|
|
3543
3770
|
email: Email,
|
|
3544
|
-
topics:
|
|
3545
|
-
source:
|
|
3771
|
+
topics: z14.array(Slug),
|
|
3772
|
+
source: z14.enum(["signup_form", "api", "dashboard", "automation"]),
|
|
3546
3773
|
signup_form_id: Id.nullable(),
|
|
3547
|
-
signup_form_version:
|
|
3774
|
+
signup_form_version: z14.number().int().min(1).nullable(),
|
|
3548
3775
|
subscribed_at: Timestamp
|
|
3549
3776
|
});
|
|
3550
|
-
var ImportFinishedData =
|
|
3777
|
+
var ImportFinishedData = z14.object({
|
|
3551
3778
|
import_id: Id,
|
|
3552
|
-
status:
|
|
3779
|
+
status: z14.enum(["completed", "cancelled", "failed"]),
|
|
3553
3780
|
result: ImportReport.nullable(),
|
|
3554
|
-
error:
|
|
3781
|
+
error: z14.string().nullable(),
|
|
3555
3782
|
finished_at: Timestamp
|
|
3556
3783
|
});
|
|
3557
|
-
var MailingScheduledData =
|
|
3784
|
+
var MailingScheduledData = z14.object({
|
|
3558
3785
|
mailing_id: Id,
|
|
3559
3786
|
mailing_metadata: MailingMetadata,
|
|
3560
3787
|
scheduled_at: Timestamp.nullable()
|
|
3561
3788
|
});
|
|
3562
|
-
var MailingStartedData =
|
|
3789
|
+
var MailingStartedData = z14.object({
|
|
3563
3790
|
mailing_id: Id,
|
|
3564
3791
|
mailing_metadata: MailingMetadata,
|
|
3565
|
-
trigger:
|
|
3566
|
-
recipients:
|
|
3792
|
+
trigger: z14.enum(["send", "schedule"]),
|
|
3793
|
+
recipients: z14.number().int().min(0),
|
|
3567
3794
|
started_at: Timestamp
|
|
3568
3795
|
});
|
|
3569
|
-
var MailingScheduleFailedData =
|
|
3796
|
+
var MailingScheduleFailedData = z14.object({
|
|
3570
3797
|
mailing_id: Id,
|
|
3571
3798
|
mailing_metadata: MailingMetadata,
|
|
3572
|
-
code:
|
|
3573
|
-
message:
|
|
3799
|
+
code: z14.string(),
|
|
3800
|
+
message: z14.string(),
|
|
3574
3801
|
failed_at: Timestamp
|
|
3575
3802
|
});
|
|
3576
|
-
var MailingAbWinnerSelectedData =
|
|
3803
|
+
var MailingAbWinnerSelectedData = z14.object({
|
|
3577
3804
|
mailing_id: Id,
|
|
3578
3805
|
mailing_metadata: MailingMetadata,
|
|
3579
|
-
winner:
|
|
3580
|
-
decided_by:
|
|
3581
|
-
variants:
|
|
3806
|
+
winner: z14.string(),
|
|
3807
|
+
decided_by: z14.enum(["metric", "manual"]),
|
|
3808
|
+
variants: z14.array(VariantAnalytics),
|
|
3582
3809
|
decided_at: Timestamp
|
|
3583
3810
|
});
|
|
3584
|
-
var ContactTaggedData =
|
|
3811
|
+
var ContactTaggedData = z14.object({
|
|
3585
3812
|
contact_id: Id,
|
|
3586
|
-
external_id:
|
|
3813
|
+
external_id: z14.string().nullable(),
|
|
3587
3814
|
email: Email,
|
|
3588
3815
|
/** Only the tags the contact did not already have. */
|
|
3589
|
-
tags:
|
|
3590
|
-
source:
|
|
3816
|
+
tags: z14.array(Slug).min(1),
|
|
3817
|
+
source: z14.enum(["api", "dashboard", "signup_form", "import", "automation"]),
|
|
3591
3818
|
tagged_at: Timestamp
|
|
3592
3819
|
});
|
|
3593
|
-
var ContactUpdatedData =
|
|
3820
|
+
var ContactUpdatedData = z14.object({
|
|
3594
3821
|
contact_id: Id,
|
|
3595
|
-
external_id:
|
|
3822
|
+
external_id: z14.string().nullable(),
|
|
3596
3823
|
email: Email,
|
|
3597
|
-
changed:
|
|
3824
|
+
changed: z14.array(z14.string()).min(1),
|
|
3598
3825
|
properties: Properties,
|
|
3599
|
-
source:
|
|
3826
|
+
source: z14.enum(["api", "dashboard", "signup_form", "import", "automation"]),
|
|
3600
3827
|
updated_at: Timestamp
|
|
3601
3828
|
});
|
|
3602
|
-
var AutomationRunEventBase =
|
|
3829
|
+
var AutomationRunEventBase = z14.object({
|
|
3603
3830
|
automation_id: Id,
|
|
3604
|
-
automation_name:
|
|
3831
|
+
automation_name: z14.string(),
|
|
3605
3832
|
run_id: Id,
|
|
3606
|
-
version:
|
|
3833
|
+
version: z14.number().int().min(1),
|
|
3607
3834
|
contact_id: Id,
|
|
3608
|
-
external_id:
|
|
3835
|
+
external_id: z14.string().nullable(),
|
|
3609
3836
|
email: Email
|
|
3610
3837
|
});
|
|
3611
3838
|
var AutomationRunStartedData = AutomationRunEventBase.extend({
|
|
3612
3839
|
/** What let them in: a trigger kind, or `manual` for an explicit enrolment. */
|
|
3613
|
-
trigger_kind:
|
|
3840
|
+
trigger_kind: z14.string(),
|
|
3614
3841
|
entered_at: Timestamp
|
|
3615
3842
|
});
|
|
3616
3843
|
var AutomationRunCompletedData = AutomationRunEventBase.extend({
|
|
3617
|
-
last_step_id:
|
|
3844
|
+
last_step_id: z14.string().nullable(),
|
|
3618
3845
|
completed_at: Timestamp
|
|
3619
3846
|
});
|
|
3620
3847
|
var AutomationRunExitedData = AutomationRunEventBase.extend({
|
|
3621
3848
|
reason: AutomationEndReason,
|
|
3622
|
-
step_id:
|
|
3849
|
+
step_id: z14.string().nullable(),
|
|
3623
3850
|
exited_at: Timestamp
|
|
3624
3851
|
});
|
|
3625
3852
|
var AutomationRunFailedData = AutomationRunEventBase.extend({
|
|
3626
|
-
reason:
|
|
3627
|
-
step_id:
|
|
3853
|
+
reason: z14.string(),
|
|
3854
|
+
step_id: z14.string().nullable(),
|
|
3628
3855
|
failed_at: Timestamp
|
|
3629
3856
|
});
|
|
3630
3857
|
var AutomationWebhookData = AutomationRunEventBase.extend({
|
|
3631
|
-
step_id:
|
|
3858
|
+
step_id: z14.string(),
|
|
3632
3859
|
/** The step's own label, when it was given one, so a receiver can branch on a name. */
|
|
3633
|
-
step_label:
|
|
3860
|
+
step_label: z14.string().nullable(),
|
|
3634
3861
|
/** Whatever the step carries, verbatim. Null when it carries nothing. */
|
|
3635
3862
|
payload: JsonValue.nullable(),
|
|
3636
3863
|
fired_at: Timestamp
|
|
3637
3864
|
});
|
|
3638
|
-
var TemplateEventBase =
|
|
3865
|
+
var TemplateEventBase = z14.object({
|
|
3639
3866
|
template_id: Id,
|
|
3640
|
-
name:
|
|
3641
|
-
version:
|
|
3642
|
-
source:
|
|
3867
|
+
name: z14.string().min(1).max(200),
|
|
3868
|
+
version: z14.number().int().min(1),
|
|
3869
|
+
source: z14.enum(["api", "dashboard", "mjml_import"])
|
|
3643
3870
|
});
|
|
3644
3871
|
var TemplateCreatedData = TemplateEventBase.extend({ created_at: Timestamp });
|
|
3645
|
-
var TemplateUpdatedData = TemplateEventBase.extend({ archived:
|
|
3872
|
+
var TemplateUpdatedData = TemplateEventBase.extend({ archived: z14.boolean(), updated_at: Timestamp });
|
|
3646
3873
|
var TemplateDeletedData = TemplateEventBase.extend({ deleted_at: Timestamp });
|
|
3647
|
-
var envelope = (type, data) =>
|
|
3874
|
+
var envelope = (type, data) => z14.object({
|
|
3648
3875
|
/** Unique per event: a receiver deduplicates on it (deliveries may repeat). */
|
|
3649
3876
|
id: Id,
|
|
3650
|
-
type:
|
|
3877
|
+
type: z14.literal(type),
|
|
3651
3878
|
created_at: Timestamp,
|
|
3652
3879
|
workspace_id: Id,
|
|
3653
3880
|
data
|
|
3654
3881
|
});
|
|
3655
|
-
var WebhookEvent =
|
|
3882
|
+
var WebhookEvent = z14.discriminatedUnion("type", [
|
|
3656
3883
|
envelope("message.sent", MessageSentData),
|
|
3657
3884
|
envelope("message.failed", MessageFailedData),
|
|
3658
3885
|
envelope("contact.unsubscribed", ContactUnsubscribedData),
|
|
@@ -3676,16 +3903,16 @@ var WebhookEvent = z13.discriminatedUnion("type", [
|
|
|
3676
3903
|
envelope("template.updated", TemplateUpdatedData),
|
|
3677
3904
|
envelope("template.deleted", TemplateDeletedData)
|
|
3678
3905
|
]);
|
|
3679
|
-
var WebhookEndpoint =
|
|
3906
|
+
var WebhookEndpoint = z14.object({
|
|
3680
3907
|
id: Id,
|
|
3681
|
-
url:
|
|
3682
|
-
description:
|
|
3683
|
-
events:
|
|
3684
|
-
enabled:
|
|
3908
|
+
url: z14.string().url(),
|
|
3909
|
+
description: z14.string().max(500).nullable(),
|
|
3910
|
+
events: z14.array(WebhookEventType).min(1),
|
|
3911
|
+
enabled: z14.boolean(),
|
|
3685
3912
|
created_at: Timestamp,
|
|
3686
3913
|
updated_at: Timestamp
|
|
3687
3914
|
});
|
|
3688
|
-
var WebhookUrl =
|
|
3915
|
+
var WebhookUrl = z14.string().url().refine((u) => {
|
|
3689
3916
|
try {
|
|
3690
3917
|
const url = new URL(u);
|
|
3691
3918
|
if (url.protocol === "https:") return true;
|
|
@@ -3694,35 +3921,35 @@ var WebhookUrl = z13.string().url().refine((u) => {
|
|
|
3694
3921
|
return false;
|
|
3695
3922
|
}
|
|
3696
3923
|
}, "must be https (http only for localhost)");
|
|
3697
|
-
var WebhookEndpointCreate =
|
|
3924
|
+
var WebhookEndpointCreate = z14.object({
|
|
3698
3925
|
url: WebhookUrl,
|
|
3699
|
-
description:
|
|
3700
|
-
events:
|
|
3701
|
-
enabled:
|
|
3926
|
+
description: z14.string().max(500).optional(),
|
|
3927
|
+
events: z14.array(WebhookEventType).min(1),
|
|
3928
|
+
enabled: z14.boolean().optional()
|
|
3702
3929
|
});
|
|
3703
|
-
var WebhookEndpointUpdate =
|
|
3930
|
+
var WebhookEndpointUpdate = z14.object({
|
|
3704
3931
|
url: WebhookUrl,
|
|
3705
|
-
description:
|
|
3706
|
-
events:
|
|
3707
|
-
enabled:
|
|
3932
|
+
description: z14.string().max(500).nullable(),
|
|
3933
|
+
events: z14.array(WebhookEventType).min(1),
|
|
3934
|
+
enabled: z14.boolean()
|
|
3708
3935
|
}).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
|
|
3709
|
-
var WebhookEndpointWithSecret =
|
|
3936
|
+
var WebhookEndpointWithSecret = z14.object({
|
|
3710
3937
|
endpoint: WebhookEndpoint,
|
|
3711
|
-
secret:
|
|
3938
|
+
secret: z14.string().min(32)
|
|
3712
3939
|
});
|
|
3713
3940
|
var WEBHOOK_DELIVERY_STATUSES = ["pending", "succeeded", "failed"];
|
|
3714
|
-
var WebhookDeliveryStatus =
|
|
3941
|
+
var WebhookDeliveryStatus = z14.enum(WEBHOOK_DELIVERY_STATUSES);
|
|
3715
3942
|
var WEBHOOK_MAX_ATTEMPTS = 8;
|
|
3716
3943
|
var WEBHOOK_RETRY_DELAYS_SECONDS = [30, 120, 600, 1800, 3600, 7200, 21600];
|
|
3717
|
-
var WebhookDelivery =
|
|
3944
|
+
var WebhookDelivery = z14.object({
|
|
3718
3945
|
id: Id,
|
|
3719
3946
|
endpoint_id: Id,
|
|
3720
3947
|
event_id: Id,
|
|
3721
3948
|
event_type: WebhookEventType,
|
|
3722
3949
|
status: WebhookDeliveryStatus,
|
|
3723
|
-
attempts:
|
|
3724
|
-
last_status_code:
|
|
3725
|
-
last_error:
|
|
3950
|
+
attempts: z14.number().int().min(0),
|
|
3951
|
+
last_status_code: z14.number().int().min(100).max(599).nullable(),
|
|
3952
|
+
last_error: z14.string().nullable(),
|
|
3726
3953
|
next_attempt_at: Timestamp.nullable(),
|
|
3727
3954
|
delivered_at: Timestamp.nullable(),
|
|
3728
3955
|
created_at: Timestamp
|
|
@@ -3731,7 +3958,7 @@ var WebhookDeliveryListQuery = PageQuery.extend({
|
|
|
3731
3958
|
status: WebhookDeliveryStatus.optional(),
|
|
3732
3959
|
event_type: WebhookEventType.optional()
|
|
3733
3960
|
});
|
|
3734
|
-
var WebhookDeliveryParams =
|
|
3961
|
+
var WebhookDeliveryParams = z14.object({ id: Id, delivery_id: Id });
|
|
3735
3962
|
|
|
3736
3963
|
// src/webhook-signing.ts
|
|
3737
3964
|
var WEBHOOK_SIGNATURE_HEADER = "x-mail-signature";
|
|
@@ -3798,79 +4025,79 @@ async function verifyWebhook(input) {
|
|
|
3798
4025
|
}
|
|
3799
4026
|
|
|
3800
4027
|
// src/billing.ts
|
|
3801
|
-
import { z as
|
|
4028
|
+
import { z as z15 } from "zod";
|
|
3802
4029
|
var PLAN_IDS = ["free", "starter", "growth", "design_partner"];
|
|
3803
|
-
var PlanId =
|
|
3804
|
-
var PlanLimits =
|
|
3805
|
-
monthly_messages:
|
|
3806
|
-
contacts:
|
|
3807
|
-
members:
|
|
3808
|
-
providers:
|
|
3809
|
-
webhook_endpoints:
|
|
4030
|
+
var PlanId = z15.enum(PLAN_IDS);
|
|
4031
|
+
var PlanLimits = z15.object({
|
|
4032
|
+
monthly_messages: z15.number().int().min(0).nullable(),
|
|
4033
|
+
contacts: z15.number().int().min(0).nullable(),
|
|
4034
|
+
members: z15.number().int().min(1).nullable(),
|
|
4035
|
+
providers: z15.number().int().min(1).nullable(),
|
|
4036
|
+
webhook_endpoints: z15.number().int().min(0).nullable(),
|
|
3810
4037
|
/** Automations that may be live (active or paused) at once (A1). */
|
|
3811
|
-
active_automations:
|
|
4038
|
+
active_automations: z15.number().int().min(0).nullable(),
|
|
3812
4039
|
/** Steps one automation may hold. Null is the flow's own cap, not unlimited. */
|
|
3813
|
-
automation_steps:
|
|
4040
|
+
automation_steps: z15.number().int().min(1).nullable()
|
|
3814
4041
|
});
|
|
3815
|
-
var PlanFeatures =
|
|
4042
|
+
var PlanFeatures = z15.object({
|
|
3816
4043
|
/** A/B tests of subject and content on a mailing (S4). */
|
|
3817
|
-
ab_testing:
|
|
4044
|
+
ab_testing: z15.boolean(),
|
|
3818
4045
|
/** Open and click tracking, still off until a workspace turns it on (`settings.tracking_enabled`). */
|
|
3819
|
-
tracking:
|
|
4046
|
+
tracking: z15.boolean(),
|
|
3820
4047
|
/** Sending and hosting pages on the workspace's own domain (a later phase). */
|
|
3821
|
-
custom_domains:
|
|
4048
|
+
custom_domains: z15.boolean(),
|
|
3822
4049
|
/** Automations: flows a person walks through step by step (A1). */
|
|
3823
|
-
automations:
|
|
4050
|
+
automations: z15.boolean()
|
|
3824
4051
|
});
|
|
3825
4052
|
var PLAN_FEATURES = ["ab_testing", "tracking", "custom_domains", "automations"];
|
|
3826
|
-
var Plan =
|
|
4053
|
+
var Plan = z15.object({
|
|
3827
4054
|
id: PlanId,
|
|
3828
|
-
name:
|
|
4055
|
+
name: z15.string().min(1),
|
|
3829
4056
|
/** Price in the smallest currency unit per month; null for plans not sold. */
|
|
3830
|
-
monthly_price_cents:
|
|
3831
|
-
currency:
|
|
4057
|
+
monthly_price_cents: z15.number().int().min(0).nullable(),
|
|
4058
|
+
currency: z15.string().length(3),
|
|
3832
4059
|
limits: PlanLimits,
|
|
3833
4060
|
features: PlanFeatures
|
|
3834
4061
|
});
|
|
3835
|
-
var CatalogPlan = Plan.extend({ sellable:
|
|
3836
|
-
var Subscription =
|
|
4062
|
+
var CatalogPlan = Plan.extend({ sellable: z15.boolean() });
|
|
4063
|
+
var Subscription = z15.object({
|
|
3837
4064
|
workspace_id: Id,
|
|
3838
4065
|
plan: PlanId,
|
|
3839
|
-
status:
|
|
4066
|
+
status: z15.enum(["active", "trialing", "past_due", "cancelled"]),
|
|
3840
4067
|
current_period_start: Timestamp,
|
|
3841
4068
|
current_period_end: Timestamp,
|
|
3842
|
-
cancel_at_period_end:
|
|
4069
|
+
cancel_at_period_end: z15.boolean(),
|
|
3843
4070
|
/**
|
|
3844
4071
|
* A design partner outside billing (plan `design_partner`, no limits, no
|
|
3845
4072
|
* checkout). Set only by an operator, never through the API.
|
|
3846
4073
|
*/
|
|
3847
|
-
billing_exempt:
|
|
4074
|
+
billing_exempt: z15.boolean()
|
|
3848
4075
|
});
|
|
3849
4076
|
var USAGE_METRICS = ["messages", "contacts", "members", "providers", "webhook_endpoints", "active_automations"];
|
|
3850
|
-
var UsageMetric =
|
|
4077
|
+
var UsageMetric = z15.enum(USAGE_METRICS);
|
|
3851
4078
|
var USAGE_WARNING_RATIO = 0.8;
|
|
3852
|
-
var UsageWarning =
|
|
4079
|
+
var UsageWarning = z15.object({
|
|
3853
4080
|
metric: UsageMetric,
|
|
3854
|
-
used:
|
|
3855
|
-
limit:
|
|
4081
|
+
used: z15.number().int().min(0),
|
|
4082
|
+
limit: z15.number().int().min(0),
|
|
3856
4083
|
/** `approaching`: at or above USAGE_WARNING_RATIO of the limit; `reached`: at or above the limit. */
|
|
3857
|
-
level:
|
|
4084
|
+
level: z15.enum(["approaching", "reached"])
|
|
3858
4085
|
});
|
|
3859
|
-
var Usage =
|
|
4086
|
+
var Usage = z15.object({
|
|
3860
4087
|
plan: PlanId,
|
|
3861
4088
|
period_start: Timestamp,
|
|
3862
4089
|
period_end: Timestamp,
|
|
3863
|
-
metrics:
|
|
3864
|
-
warnings:
|
|
4090
|
+
metrics: z15.record(UsageMetric, z15.object({ used: z15.number().int().min(0), limit: z15.number().int().min(0).nullable() })),
|
|
4091
|
+
warnings: z15.array(UsageWarning)
|
|
3865
4092
|
});
|
|
3866
|
-
var CheckoutRequest =
|
|
3867
|
-
plan:
|
|
3868
|
-
success_url:
|
|
3869
|
-
cancel_url:
|
|
4093
|
+
var CheckoutRequest = z15.object({
|
|
4094
|
+
plan: z15.enum(["starter", "growth"]),
|
|
4095
|
+
success_url: z15.string().url(),
|
|
4096
|
+
cancel_url: z15.string().url()
|
|
3870
4097
|
});
|
|
3871
|
-
var CheckoutSession =
|
|
3872
|
-
var PortalRequest =
|
|
3873
|
-
var PortalSession =
|
|
4098
|
+
var CheckoutSession = z15.object({ url: z15.string().url() });
|
|
4099
|
+
var PortalRequest = z15.object({ return_url: z15.string().url() });
|
|
4100
|
+
var PortalSession = z15.object({ url: z15.string().url() });
|
|
3874
4101
|
var USAGE_WARNING_HEADER = "x-mail-usage-warning";
|
|
3875
4102
|
function formatUsageWarningHeader(warnings) {
|
|
3876
4103
|
if (warnings.length === 0) return null;
|
|
@@ -3890,7 +4117,7 @@ function parseUsageWarningHeader(value) {
|
|
|
3890
4117
|
}
|
|
3891
4118
|
|
|
3892
4119
|
// src/routes.ts
|
|
3893
|
-
import { z as
|
|
4120
|
+
import { z as z16 } from "zod";
|
|
3894
4121
|
var list = (item) => page(item);
|
|
3895
4122
|
var foundationRoutes = {
|
|
3896
4123
|
"workspaces.create": {
|
|
@@ -4047,6 +4274,25 @@ var templateRoutes = {
|
|
|
4047
4274
|
access: "write",
|
|
4048
4275
|
phase: "S1"
|
|
4049
4276
|
},
|
|
4277
|
+
/**
|
|
4278
|
+
* The sending app declares what the template receives (see
|
|
4279
|
+
* `template-inputs.ts`). Replaces the stored declaration whole; the same
|
|
4280
|
+
* declaration again is a no-op (`changed: false`). Creates no template
|
|
4281
|
+
* version and takes no `base_version`, so it never conflicts with an edit.
|
|
4282
|
+
* An API key only (`send` or `full`): the declaration is the app's statement
|
|
4283
|
+
* about its own code, so a dashboard member is refused with `forbidden`, a
|
|
4284
|
+
* rule the access levels cannot express and the service enforces itself.
|
|
4285
|
+
*/
|
|
4286
|
+
"templates.declareInputs": {
|
|
4287
|
+
method: "PUT",
|
|
4288
|
+
path: "/v1/templates/:id/inputs",
|
|
4289
|
+
params: IdParams,
|
|
4290
|
+
body: TemplateInputsDeclaration,
|
|
4291
|
+
response: TemplateInputsDeclared,
|
|
4292
|
+
status: 200,
|
|
4293
|
+
access: "write",
|
|
4294
|
+
phase: "S1"
|
|
4295
|
+
},
|
|
4050
4296
|
"templates.delete": {
|
|
4051
4297
|
method: "DELETE",
|
|
4052
4298
|
path: "/v1/templates/:id",
|
|
@@ -4098,7 +4344,7 @@ var templateRoutes = {
|
|
|
4098
4344
|
path: "/v1/templates/:id/export",
|
|
4099
4345
|
params: IdParams,
|
|
4100
4346
|
query: TemplateExportQuery,
|
|
4101
|
-
response:
|
|
4347
|
+
response: z16.string(),
|
|
4102
4348
|
responseType: "text",
|
|
4103
4349
|
status: 200,
|
|
4104
4350
|
access: "read",
|
|
@@ -4422,7 +4668,7 @@ var sendingRoutes = {
|
|
|
4422
4668
|
path: "/v1/mailings/:id/export",
|
|
4423
4669
|
params: IdParams,
|
|
4424
4670
|
query: MailingExportQuery,
|
|
4425
|
-
response:
|
|
4671
|
+
response: z16.string(),
|
|
4426
4672
|
responseType: "text",
|
|
4427
4673
|
status: 200,
|
|
4428
4674
|
access: "read",
|
|
@@ -4655,7 +4901,7 @@ var platformRoutes = {
|
|
|
4655
4901
|
"contactProperties.list": {
|
|
4656
4902
|
method: "GET",
|
|
4657
4903
|
path: "/v1/contact-properties",
|
|
4658
|
-
response:
|
|
4904
|
+
response: z16.object({ data: z16.array(ContactPropertyDefinition) }),
|
|
4659
4905
|
status: 200,
|
|
4660
4906
|
access: "read",
|
|
4661
4907
|
phase: "S4"
|
|
@@ -4954,7 +5200,7 @@ var billingRoutes = {
|
|
|
4954
5200
|
"billing.plans": {
|
|
4955
5201
|
method: "GET",
|
|
4956
5202
|
path: "/v1/billing/plans",
|
|
4957
|
-
response:
|
|
5203
|
+
response: z16.object({ data: z16.array(CatalogPlan) }),
|
|
4958
5204
|
status: 200,
|
|
4959
5205
|
access: "read",
|
|
4960
5206
|
phase: "S5"
|
|
@@ -5096,7 +5342,7 @@ var automationRoutes = {
|
|
|
5096
5342
|
method: "POST",
|
|
5097
5343
|
path: "/v1/automations/:id/pause",
|
|
5098
5344
|
params: IdParams,
|
|
5099
|
-
body:
|
|
5345
|
+
body: z16.object({}).strict(),
|
|
5100
5346
|
response: Automation,
|
|
5101
5347
|
status: 200,
|
|
5102
5348
|
access: "write",
|
|
@@ -5106,7 +5352,7 @@ var automationRoutes = {
|
|
|
5106
5352
|
method: "POST",
|
|
5107
5353
|
path: "/v1/automations/:id/resume",
|
|
5108
5354
|
params: IdParams,
|
|
5109
|
-
body:
|
|
5355
|
+
body: z16.object({}).strict(),
|
|
5110
5356
|
response: Automation,
|
|
5111
5357
|
status: 200,
|
|
5112
5358
|
access: "write",
|
|
@@ -5116,7 +5362,7 @@ var automationRoutes = {
|
|
|
5116
5362
|
method: "POST",
|
|
5117
5363
|
path: "/v1/automations/:id/archive",
|
|
5118
5364
|
params: IdParams,
|
|
5119
|
-
body:
|
|
5365
|
+
body: z16.object({}).strict(),
|
|
5120
5366
|
response: Automation,
|
|
5121
5367
|
status: 200,
|
|
5122
5368
|
access: "write",
|
|
@@ -5495,6 +5741,13 @@ export {
|
|
|
5495
5741
|
MAX_STEPS,
|
|
5496
5742
|
MAX_STEP_VISITS,
|
|
5497
5743
|
MAX_SUBJECT_LENGTH,
|
|
5744
|
+
MAX_TEMPLATE_INPUTS,
|
|
5745
|
+
MAX_TEMPLATE_INPUTS_APP_LENGTH,
|
|
5746
|
+
MAX_TEMPLATE_INPUTS_SEEN,
|
|
5747
|
+
MAX_TEMPLATE_INPUT_EXAMPLE_LENGTH,
|
|
5748
|
+
MAX_TEMPLATE_INPUT_LABEL_LENGTH,
|
|
5749
|
+
MAX_TEMPLATE_INPUT_NOTE_LENGTH,
|
|
5750
|
+
MAX_TEMPLATE_INPUT_VALUES,
|
|
5498
5751
|
MAX_TEMPLATE_TRANSLATIONS,
|
|
5499
5752
|
MAX_TRIGGERS,
|
|
5500
5753
|
MEMBER_ROLES,
|
|
@@ -5589,6 +5842,7 @@ export {
|
|
|
5589
5842
|
RecipientStatus,
|
|
5590
5843
|
RemoteImage,
|
|
5591
5844
|
ResendSigningSecret,
|
|
5845
|
+
SERVICE_FILLED_MERGE_FIELDS,
|
|
5592
5846
|
SIGNUP_FORM_FIELDS,
|
|
5593
5847
|
SKIP_REASONS,
|
|
5594
5848
|
SMTP_SECURITY,
|
|
@@ -5618,6 +5872,8 @@ export {
|
|
|
5618
5872
|
SuppressionCreate,
|
|
5619
5873
|
SuppressionListQuery,
|
|
5620
5874
|
SuppressionReason,
|
|
5875
|
+
TEMPLATE_INPUT_KINDS,
|
|
5876
|
+
TEMPLATE_INPUT_PROBLEM_REASONS,
|
|
5621
5877
|
TEMPLATE_LANGUAGE_STATUSES,
|
|
5622
5878
|
TERMINAL_MAILING_STATUSES,
|
|
5623
5879
|
Tag,
|
|
@@ -5634,6 +5890,16 @@ export {
|
|
|
5634
5890
|
TemplateImportPreview,
|
|
5635
5891
|
TemplateImportPreviewRequest,
|
|
5636
5892
|
TemplateImportResult,
|
|
5893
|
+
TemplateInput,
|
|
5894
|
+
TemplateInputMap,
|
|
5895
|
+
TemplateInputName,
|
|
5896
|
+
TemplateInputSeen,
|
|
5897
|
+
TemplateInputSource,
|
|
5898
|
+
TemplateInputValue,
|
|
5899
|
+
TemplateInputs,
|
|
5900
|
+
TemplateInputsDeclaration,
|
|
5901
|
+
TemplateInputsDeclared,
|
|
5902
|
+
TemplateInputsSeen,
|
|
5637
5903
|
TemplateLanguageStatus,
|
|
5638
5904
|
TemplateListQuery,
|
|
5639
5905
|
TemplatePreheader,
|
|
@@ -5787,6 +6053,10 @@ export {
|
|
|
5787
6053
|
renameInsertionChecked,
|
|
5788
6054
|
renameInsertionChoice,
|
|
5789
6055
|
renameInsertionChoiceChecked,
|
|
6056
|
+
renameInsertionEverywhere,
|
|
6057
|
+
renameInsertionPlacements,
|
|
6058
|
+
renameTokenInDocument,
|
|
6059
|
+
renameTokenInText,
|
|
5790
6060
|
resolveInsertions,
|
|
5791
6061
|
restorePatch,
|
|
5792
6062
|
routes,
|
|
@@ -5802,6 +6072,8 @@ export {
|
|
|
5802
6072
|
stepTargets,
|
|
5803
6073
|
storedTemplateLine,
|
|
5804
6074
|
stronglyConnected,
|
|
6075
|
+
templateInputDrift,
|
|
6076
|
+
templateInputProblems,
|
|
5805
6077
|
templateRoutes,
|
|
5806
6078
|
testGroupSize,
|
|
5807
6079
|
tightLoops,
|
|
@@ -5809,6 +6081,7 @@ export {
|
|
|
5809
6081
|
translationInsertionProblems,
|
|
5810
6082
|
translationTextFromInput,
|
|
5811
6083
|
triggerKey,
|
|
6084
|
+
unknownRefusedInsertions,
|
|
5812
6085
|
unplacedInsertions,
|
|
5813
6086
|
validateAutomation,
|
|
5814
6087
|
verifyWebhook
|