@marlinjai/mail-contract 0.3.0 → 0.5.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 +9117 -2230
- package/dist/index.d.ts +9117 -2230
- package/dist/index.js +449 -19
- package/dist/index.mjs +413 -19
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -22,6 +22,29 @@ function page(item) {
|
|
|
22
22
|
}
|
|
23
23
|
var IdParams = z.object({ id: Id });
|
|
24
24
|
var Ok = z.object({ ok: z.literal(true) });
|
|
25
|
+
var LOCALE_TAG_PATTERN = /^[A-Za-z]{2,3}(?:-[A-Za-z]{4})?(?:-(?:[A-Za-z]{2}|[0-9]{3}))?$/;
|
|
26
|
+
var LocaleTag = z.string().min(2).max(35).regex(LOCALE_TAG_PATTERN, "a language tag such as en, fr or pt-BR");
|
|
27
|
+
function canonicalLocale(tag) {
|
|
28
|
+
if (typeof tag !== "string") return null;
|
|
29
|
+
const trimmed = tag.trim().replace(/_/g, "-");
|
|
30
|
+
if (!LOCALE_TAG_PATTERN.test(trimmed)) return null;
|
|
31
|
+
return trimmed.split("-").map((part, i) => {
|
|
32
|
+
if (i === 0) return part.toLowerCase();
|
|
33
|
+
if (part.length === 4) return part[0].toUpperCase() + part.slice(1).toLowerCase();
|
|
34
|
+
return part.toUpperCase();
|
|
35
|
+
}).join("-");
|
|
36
|
+
}
|
|
37
|
+
function primaryLanguage(tag) {
|
|
38
|
+
return canonicalLocale(tag)?.split("-")[0] ?? null;
|
|
39
|
+
}
|
|
40
|
+
function matchLocale(wanted, available) {
|
|
41
|
+
const canonical = canonicalLocale(wanted);
|
|
42
|
+
if (!canonical) return null;
|
|
43
|
+
const exact = available.find((a) => canonicalLocale(a) === canonical);
|
|
44
|
+
if (exact !== void 0) return exact;
|
|
45
|
+
const primary = canonical.split("-")[0];
|
|
46
|
+
return available.find((a) => primaryLanguage(a) === primary) ?? null;
|
|
47
|
+
}
|
|
25
48
|
|
|
26
49
|
// src/errors.ts
|
|
27
50
|
import { z as z2 } from "zod";
|
|
@@ -137,32 +160,71 @@ var TemplateDocument = z3.object({
|
|
|
137
160
|
sections: z3.array(z3.record(z3.unknown()))
|
|
138
161
|
}).passthrough();
|
|
139
162
|
var MAX_DOCUMENT_BYTES = 1e6;
|
|
163
|
+
var MAX_SUBJECT_LENGTH = 998;
|
|
164
|
+
var MAX_PREHEADER_LENGTH = 500;
|
|
165
|
+
var TemplateSubject = z3.string().max(MAX_SUBJECT_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
|
|
166
|
+
var TemplatePreheader = z3.string().max(MAX_PREHEADER_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
|
|
167
|
+
var TEMPLATE_LANGUAGE_STATUSES = ["draft", "ready"];
|
|
168
|
+
var TemplateLanguageStatus = z3.enum(TEMPLATE_LANGUAGE_STATUSES);
|
|
169
|
+
var TemplateTranslation = z3.object({
|
|
170
|
+
subject: TemplateSubject.nullable(),
|
|
171
|
+
preheader: TemplatePreheader.nullable(),
|
|
172
|
+
document: TemplateDocument,
|
|
173
|
+
status: TemplateLanguageStatus
|
|
174
|
+
});
|
|
175
|
+
var MAX_TEMPLATE_TRANSLATIONS = 19;
|
|
176
|
+
var TemplateTranslations = z3.record(LocaleTag, TemplateTranslation).refine((t) => Object.keys(t).length <= MAX_TEMPLATE_TRANSLATIONS, `at most ${MAX_TEMPLATE_TRANSLATIONS} language versions`);
|
|
140
177
|
var Template = z3.object({
|
|
141
178
|
id: Id,
|
|
142
179
|
name: z3.string().min(1).max(200),
|
|
143
180
|
description: z3.string().max(2e3).nullable(),
|
|
181
|
+
/**
|
|
182
|
+
* The main language: what `subject`, `preheader` and `document` are written
|
|
183
|
+
* in, and what every recipient without a ready version of their own gets.
|
|
184
|
+
* Set from the workspace's `default_locale` when the template is created
|
|
185
|
+
* unless given, and never changed by a later change of that setting.
|
|
186
|
+
*/
|
|
187
|
+
locale: LocaleTag,
|
|
188
|
+
/** The main language's subject line; null when the mailing that uses the template gives one. */
|
|
189
|
+
subject: TemplateSubject.nullable(),
|
|
190
|
+
preheader: TemplatePreheader.nullable(),
|
|
144
191
|
document: TemplateDocument,
|
|
145
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* The other languages. A version whose language the workspace no longer
|
|
194
|
+
* lists is kept, but no send uses it until the language is listed again.
|
|
195
|
+
*/
|
|
196
|
+
translations: TemplateTranslations,
|
|
197
|
+
/** Increments on every saved change, of any language. */
|
|
146
198
|
version: z3.number().int().min(1),
|
|
147
199
|
thumbnail_url: z3.string().url().nullable(),
|
|
148
200
|
archived_at: Timestamp.nullable(),
|
|
149
201
|
created_at: Timestamp,
|
|
150
202
|
updated_at: Timestamp
|
|
151
203
|
});
|
|
152
|
-
var TemplateSummary = Template.omit({ document: true })
|
|
204
|
+
var TemplateSummary = Template.omit({ document: true, translations: true }).extend({
|
|
205
|
+
languages: z3.record(LocaleTag, TemplateLanguageStatus)
|
|
206
|
+
});
|
|
153
207
|
var TemplateCreate = z3.object({
|
|
154
208
|
name: z3.string().min(1).max(200),
|
|
155
209
|
description: z3.string().max(2e3).optional(),
|
|
156
|
-
|
|
210
|
+
locale: LocaleTag.optional(),
|
|
211
|
+
subject: TemplateSubject.nullable().optional(),
|
|
212
|
+
preheader: TemplatePreheader.nullable().optional(),
|
|
213
|
+
document: TemplateDocument,
|
|
214
|
+
translations: TemplateTranslations.optional()
|
|
157
215
|
});
|
|
158
216
|
var TemplateUpdate = z3.object({
|
|
159
217
|
base_version: z3.number().int().min(1),
|
|
160
218
|
name: z3.string().min(1).max(200).optional(),
|
|
161
219
|
description: z3.string().max(2e3).nullable().optional(),
|
|
220
|
+
locale: LocaleTag.optional(),
|
|
221
|
+
subject: TemplateSubject.nullable().optional(),
|
|
222
|
+
preheader: TemplatePreheader.nullable().optional(),
|
|
162
223
|
document: TemplateDocument.optional(),
|
|
224
|
+
translations: z3.record(LocaleTag, TemplateTranslation.nullable()).optional(),
|
|
163
225
|
archived: z3.boolean().optional()
|
|
164
226
|
}).refine(
|
|
165
|
-
(v) => v.name !== void 0 || v.description !== void 0 || v.document !== void 0 || v.archived !== void 0,
|
|
227
|
+
(v) => v.name !== void 0 || v.description !== void 0 || v.locale !== void 0 || v.subject !== void 0 || v.preheader !== void 0 || v.document !== void 0 || v.translations !== void 0 || v.archived !== void 0,
|
|
166
228
|
"at least one field besides base_version"
|
|
167
229
|
);
|
|
168
230
|
var SavedSectionBody = z3.record(z3.unknown());
|
|
@@ -192,7 +254,11 @@ var TemplateListQuery = PageQuery.extend({
|
|
|
192
254
|
var TemplateVersion = z3.object({
|
|
193
255
|
template_id: Id,
|
|
194
256
|
version: z3.number().int().min(1),
|
|
257
|
+
locale: LocaleTag,
|
|
258
|
+
subject: TemplateSubject.nullable(),
|
|
259
|
+
preheader: TemplatePreheader.nullable(),
|
|
195
260
|
document: TemplateDocument,
|
|
261
|
+
translations: TemplateTranslations,
|
|
196
262
|
created_by: z3.string().nullable(),
|
|
197
263
|
created_at: Timestamp
|
|
198
264
|
});
|
|
@@ -213,7 +279,8 @@ var CompileResult = z3.object({
|
|
|
213
279
|
errors: z3.array(CompileMessage)
|
|
214
280
|
});
|
|
215
281
|
var TemplateCompileRequest = z3.object({
|
|
216
|
-
version: z3.number().int().min(1).optional()
|
|
282
|
+
version: z3.number().int().min(1).optional(),
|
|
283
|
+
locale: LocaleTag.optional()
|
|
217
284
|
});
|
|
218
285
|
var CompileRequest = z3.object({
|
|
219
286
|
document: TemplateDocument
|
|
@@ -305,7 +372,8 @@ var EXPORT_FORMATS = ["mjml", "html"];
|
|
|
305
372
|
var ExportFormat = z3.enum(EXPORT_FORMATS);
|
|
306
373
|
var TemplateExportQuery = z3.object({
|
|
307
374
|
format: ExportFormat,
|
|
308
|
-
version: z3.coerce.number().int().min(1).optional()
|
|
375
|
+
version: z3.coerce.number().int().min(1).optional(),
|
|
376
|
+
locale: LocaleTag.optional()
|
|
309
377
|
});
|
|
310
378
|
var MailingExportQuery = z3.object({ format: ExportFormat });
|
|
311
379
|
var EXPORT_CONTENT_TYPES = {
|
|
@@ -436,10 +504,35 @@ var Mailing = z5.object({
|
|
|
436
504
|
name: z5.string().max(200).nullable(),
|
|
437
505
|
subject: z5.string().min(1).max(998),
|
|
438
506
|
preheader: z5.string().max(500).nullable(),
|
|
439
|
-
/**
|
|
507
|
+
/**
|
|
508
|
+
* The template the document was taken from, if any. The snapshot is what is
|
|
509
|
+
* sent in the main language; the template's other language versions are
|
|
510
|
+
* taken, the ready ones, when the mailing starts (see "Languages" in the
|
|
511
|
+
* contract's documentation).
|
|
512
|
+
*/
|
|
440
513
|
template_id: Id.nullable(),
|
|
441
514
|
document: TemplateDocument,
|
|
442
|
-
|
|
515
|
+
/**
|
|
516
|
+
* The main language: what `subject`, `preheader` and `document` are written
|
|
517
|
+
* in, and what every recipient without a ready version in their own language
|
|
518
|
+
* gets. The template's main language, or the workspace's default for a
|
|
519
|
+
* mailing made from a document.
|
|
520
|
+
*/
|
|
521
|
+
locale: LocaleTag,
|
|
522
|
+
/**
|
|
523
|
+
* The language every recipient is treated as reading, or null to read each
|
|
524
|
+
* recipient's own (their `merge.locale`, else their contact's language). A
|
|
525
|
+
* letter sets it to the language its one reader wrote in; a broadcast can use
|
|
526
|
+
* it to send one language version to everyone. It is matched like anyone's
|
|
527
|
+
* language: without a ready version in it, the main language goes out.
|
|
528
|
+
*/
|
|
529
|
+
send_locale: LocaleTag.nullable(),
|
|
530
|
+
/**
|
|
531
|
+
* The topic the mailing is sent under. Null for a letter (a one-to-one
|
|
532
|
+
* mailing with at most one recipient, which belongs to no topic and whose
|
|
533
|
+
* unsubscribe link offers every topic) and for an automation's notification.
|
|
534
|
+
*/
|
|
535
|
+
topic: Slug.nullable(),
|
|
443
536
|
provider_id: Id,
|
|
444
537
|
status: MailingStatus,
|
|
445
538
|
counts: MailingCounts,
|
|
@@ -464,23 +557,37 @@ var MailingContent = {
|
|
|
464
557
|
name: z5.string().max(200).optional(),
|
|
465
558
|
subject: z5.string().min(1).max(998),
|
|
466
559
|
preheader: z5.string().max(500).optional(),
|
|
467
|
-
|
|
560
|
+
/** See `Mailing.send_locale`. */
|
|
561
|
+
send_locale: LocaleTag.nullable().optional(),
|
|
562
|
+
/**
|
|
563
|
+
* Omitted or null makes the mailing a letter: one recipient at most, sent to
|
|
564
|
+
* anybody not blocked on every topic, with an unsubscribe link that offers
|
|
565
|
+
* every topic and no List-Unsubscribe headers. See "Letters" in the
|
|
566
|
+
* contract's documentation.
|
|
567
|
+
*/
|
|
568
|
+
topic: Slug.nullable().optional(),
|
|
468
569
|
provider_id: Id,
|
|
469
570
|
metadata: MailingMetadata.optional()
|
|
470
571
|
};
|
|
471
572
|
var MailingCreate = z5.object({
|
|
472
573
|
...MailingContent,
|
|
574
|
+
subject: MailingContent.subject.optional(),
|
|
473
575
|
document: TemplateDocument.optional(),
|
|
474
576
|
template_id: Id.optional()
|
|
475
577
|
}).refine((v) => v.document === void 0 !== (v.template_id === void 0), {
|
|
476
578
|
message: "exactly one of document or template_id",
|
|
477
579
|
path: ["document"]
|
|
580
|
+
}).refine((v) => v.template_id !== void 0 || v.subject !== void 0, {
|
|
581
|
+
message: "a mailing made from a document needs a subject",
|
|
582
|
+
path: ["subject"]
|
|
478
583
|
});
|
|
479
584
|
var MailingUpdate = z5.object({
|
|
480
585
|
name: z5.string().max(200).nullable().optional(),
|
|
481
586
|
subject: MailingContent.subject.optional(),
|
|
482
587
|
preheader: z5.string().max(500).nullable().optional(),
|
|
483
|
-
|
|
588
|
+
/** Null turns a draft into a letter, which it can only be with at most one recipient. */
|
|
589
|
+
topic: Slug.nullable().optional(),
|
|
590
|
+
send_locale: LocaleTag.nullable().optional(),
|
|
484
591
|
provider_id: Id.optional(),
|
|
485
592
|
metadata: MailingMetadata.optional(),
|
|
486
593
|
document: TemplateDocument.optional()
|
|
@@ -503,7 +610,8 @@ var MailingRetryFailedRequest = z5.object({
|
|
|
503
610
|
var MailingActionRequest = z5.object({}).strict();
|
|
504
611
|
var MailingTestRequest = z5.object({
|
|
505
612
|
to: Email,
|
|
506
|
-
merge: Properties.optional()
|
|
613
|
+
merge: Properties.optional(),
|
|
614
|
+
locale: LocaleTag.optional()
|
|
507
615
|
});
|
|
508
616
|
var MailingTestResult = z5.object({
|
|
509
617
|
message_id: Id,
|
|
@@ -528,6 +636,8 @@ var Recipient = z5.object({
|
|
|
528
636
|
updated_at: Timestamp
|
|
529
637
|
});
|
|
530
638
|
var MAX_RECIPIENTS_PER_BATCH = 1e3;
|
|
639
|
+
var MAX_LETTER_RECIPIENTS = 1;
|
|
640
|
+
var LETTER_RECIPIENT_REFUSAL = "letter_has_one_recipient";
|
|
531
641
|
var RecipientInput = z5.object({
|
|
532
642
|
contact_id: Id.optional(),
|
|
533
643
|
external_id: z5.string().min(1).max(255).optional(),
|
|
@@ -568,9 +678,27 @@ var MessageSummary = z5.object({
|
|
|
568
678
|
error: z5.string().nullable(),
|
|
569
679
|
is_test: z5.boolean(),
|
|
570
680
|
recipient_count: z5.number().int().min(1),
|
|
681
|
+
/** The language version the message was sent in; null for messages archived before languages existed. */
|
|
682
|
+
locale: LocaleTag.nullable(),
|
|
571
683
|
created_at: Timestamp
|
|
572
684
|
});
|
|
573
685
|
var Message = MessageSummary.extend({ html: z5.string() });
|
|
686
|
+
var MailingLanguageStatus = z5.enum(["main", "ready", "draft", "not_a_workspace_language"]);
|
|
687
|
+
var MailingLanguage = z5.object({
|
|
688
|
+
locale: LocaleTag,
|
|
689
|
+
status: MailingLanguageStatus,
|
|
690
|
+
/** Queued recipients who will get this version. */
|
|
691
|
+
planned: z5.number().int().min(0),
|
|
692
|
+
/** Messages already sent in this language (tests excluded). */
|
|
693
|
+
sent: z5.number().int().min(0)
|
|
694
|
+
});
|
|
695
|
+
var MailingLanguageReport = z5.object({
|
|
696
|
+
main_locale: LocaleTag,
|
|
697
|
+
send_locale: LocaleTag.nullable(),
|
|
698
|
+
languages: z5.array(MailingLanguage),
|
|
699
|
+
fallback: z5.array(z5.object({ wanted: z5.string(), planned: z5.number().int().min(1) })),
|
|
700
|
+
no_language: z5.number().int().min(0)
|
|
701
|
+
});
|
|
574
702
|
var MessageListQuery = PageQuery.extend({
|
|
575
703
|
mailing_id: Id.optional(),
|
|
576
704
|
outcome: MessageOutcome.optional()
|
|
@@ -1080,15 +1208,22 @@ var Unchosen = z7.literal("");
|
|
|
1080
1208
|
var DraftSlug = z7.union([Slug, Unchosen]);
|
|
1081
1209
|
var DraftPropertyKey = z7.union([ContactPropertyKey, Unchosen]);
|
|
1082
1210
|
var label = { label: z7.string().max(120).nullish() };
|
|
1211
|
+
var EmailStepLanguage = z7.object({
|
|
1212
|
+
subject: z7.string().min(1).max(998),
|
|
1213
|
+
preheader: z7.string().max(500).nullable(),
|
|
1214
|
+
document: TemplateDocument
|
|
1215
|
+
}).strict();
|
|
1083
1216
|
var EmailStep = z7.object({
|
|
1084
1217
|
kind: z7.literal("email"),
|
|
1085
1218
|
...label,
|
|
1086
1219
|
...next,
|
|
1087
1220
|
template_id: Id.nullish(),
|
|
1088
1221
|
document: TemplateDocument.nullish(),
|
|
1089
|
-
/** Empty while the step is being written; `not_chosen` refuses to publish it. */
|
|
1222
|
+
/** Empty while the step is being written, or when its template has a subject; else `not_chosen` refuses to publish it. */
|
|
1090
1223
|
subject: z7.string().max(998),
|
|
1091
|
-
preheader: z7.string().max(500).nullish()
|
|
1224
|
+
preheader: z7.string().max(500).nullish(),
|
|
1225
|
+
locale: LocaleTag.nullish(),
|
|
1226
|
+
languages: z7.record(LocaleTag, EmailStepLanguage).nullish()
|
|
1092
1227
|
}).strict();
|
|
1093
1228
|
var DelayStep = z7.object({
|
|
1094
1229
|
kind: z7.literal("delay"),
|
|
@@ -1394,7 +1529,7 @@ function validateAutomation(flow, settings, ctx) {
|
|
|
1394
1529
|
if (!step.template_id && !step.document) {
|
|
1395
1530
|
add({ code: "empty_email", step_id: id, path: [...path, "template_id"], message: "This email has neither a template nor content." });
|
|
1396
1531
|
}
|
|
1397
|
-
if (step.subject.trim() === "") {
|
|
1532
|
+
if (step.subject.trim() === "" && !(step.template_id && known.templates_with_subject?.has(step.template_id))) {
|
|
1398
1533
|
add({ code: "not_chosen", step_id: id, path: [...path, "subject"], message: "This email has no subject line." });
|
|
1399
1534
|
}
|
|
1400
1535
|
if (step.template_id) checkRef(known.templates, step.template_id, "template", [...path, "template_id"], add, id);
|
|
@@ -1879,7 +2014,7 @@ var AutomationReport = z7.object({
|
|
|
1879
2014
|
var AutomationExitRunRequest = z7.object({ reason: z7.string().max(200).optional() }).strict();
|
|
1880
2015
|
var AutomationRunParams = z7.object({ id: Id, run_id: Id });
|
|
1881
2016
|
var AutomationStepParams = z7.object({ id: Id, step_id: StepId });
|
|
1882
|
-
var AutomationTestEmailRequest = z7.object({ to: Email, merge: Properties.optional() }).strict();
|
|
2017
|
+
var AutomationTestEmailRequest = z7.object({ to: Email, merge: Properties.optional(), locale: LocaleTag.optional() }).strict();
|
|
1883
2018
|
var AutomationValidateRequest = z7.object({ flow: AutomationFlow.optional(), settings: AutomationSettings.partial().optional() }).strict();
|
|
1884
2019
|
var EventCreate = z7.object({
|
|
1885
2020
|
name: EventName,
|
|
@@ -1948,10 +2083,19 @@ var MergeDefaults = z8.record(MergeFieldName, MergeDefaultValue).refine((m) => O
|
|
|
1948
2083
|
message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
|
|
1949
2084
|
});
|
|
1950
2085
|
var WorkspaceSettings = z8.object({
|
|
1951
|
-
/**
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2086
|
+
/**
|
|
2087
|
+
* The workspace's main language (a {@link LocaleTag}, e.g. "de"): the main
|
|
2088
|
+
* language of a new template, and of the hosted pages when nothing else
|
|
2089
|
+
* decides.
|
|
2090
|
+
*/
|
|
2091
|
+
default_locale: LocaleTag,
|
|
2092
|
+
/**
|
|
2093
|
+
* The languages the workspace writes in, including `default_locale`, none
|
|
2094
|
+
* twice (compared without regard to casing). A template can have a version in
|
|
2095
|
+
* each; the hosted pages are offered in those of them they support (English,
|
|
2096
|
+
* German, French, Italian, Spanish). The service stores canonical casing.
|
|
2097
|
+
*/
|
|
2098
|
+
locales: z8.array(LocaleTag).min(1).max(20).refine((l) => new Set(l.map((t) => t.toLowerCase())).size === l.length, "a language appears twice"),
|
|
1955
2099
|
/** Open and click tracking. Off by default, opt-in per workspace. */
|
|
1956
2100
|
tracking_enabled: z8.boolean(),
|
|
1957
2101
|
/** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
|
|
@@ -2065,6 +2209,7 @@ var AUDIT_ACTIONS = [
|
|
|
2065
2209
|
"provider.anomaly_cleared",
|
|
2066
2210
|
"topic.created",
|
|
2067
2211
|
"topic.updated",
|
|
2212
|
+
"topic.deleted",
|
|
2068
2213
|
"template.created",
|
|
2069
2214
|
"template.updated",
|
|
2070
2215
|
"template.deleted",
|
|
@@ -2202,6 +2347,200 @@ var InviteAccepted = z9.object({
|
|
|
2202
2347
|
});
|
|
2203
2348
|
var INVITE_REFUSAL_REASONS = ["email_mismatch", "inviter_lacks_role", "expired", "revoked", "accepted"];
|
|
2204
2349
|
|
|
2350
|
+
// src/template-languages.ts
|
|
2351
|
+
function languageTabs(mainLocale, versions, workspaceLocales) {
|
|
2352
|
+
const main = canonicalLocale(mainLocale) ?? mainLocale;
|
|
2353
|
+
const statuses = /* @__PURE__ */ new Map();
|
|
2354
|
+
for (const [tag, status] of Object.entries(versions)) {
|
|
2355
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2356
|
+
if (canonical !== main) statuses.set(canonical, status);
|
|
2357
|
+
}
|
|
2358
|
+
const tabs = [{ locale: main, kind: "main", status: "ready" }];
|
|
2359
|
+
const seen = /* @__PURE__ */ new Set([main]);
|
|
2360
|
+
for (const tag of workspaceLocales) {
|
|
2361
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2362
|
+
if (seen.has(canonical)) continue;
|
|
2363
|
+
seen.add(canonical);
|
|
2364
|
+
tabs.push({ locale: canonical, kind: "translation", status: statuses.get(canonical) ?? "missing" });
|
|
2365
|
+
}
|
|
2366
|
+
const kept = [...statuses.keys()].filter((tag) => !seen.has(tag)).sort();
|
|
2367
|
+
for (const tag of kept) tabs.push({ locale: tag, kind: "kept", status: statuses.get(tag) });
|
|
2368
|
+
return tabs;
|
|
2369
|
+
}
|
|
2370
|
+
function languageName(tag) {
|
|
2371
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2372
|
+
try {
|
|
2373
|
+
const names = new Intl.DisplayNames(["en"], { type: "language", fallback: "none" });
|
|
2374
|
+
const name = names.of(canonical);
|
|
2375
|
+
return name && name !== canonical ? name : canonical;
|
|
2376
|
+
} catch {
|
|
2377
|
+
return canonical;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
function storedTemplateLine(value) {
|
|
2381
|
+
if (value === null || value === void 0) return null;
|
|
2382
|
+
const trimmed = value.trim();
|
|
2383
|
+
return trimmed === "" ? null : trimmed;
|
|
2384
|
+
}
|
|
2385
|
+
function buildTemplatePatch(args) {
|
|
2386
|
+
const { loaded, main, documentChanged, translations, changed, removed } = args;
|
|
2387
|
+
const out = {};
|
|
2388
|
+
const subject = storedTemplateLine(main.subject);
|
|
2389
|
+
if (subject !== storedTemplateLine(loaded.subject)) out.subject = subject;
|
|
2390
|
+
const preheader = storedTemplateLine(main.preheader);
|
|
2391
|
+
if (preheader !== storedTemplateLine(loaded.preheader)) out.preheader = preheader;
|
|
2392
|
+
if (documentChanged) out.document = main.document;
|
|
2393
|
+
const patch = {};
|
|
2394
|
+
for (const tag of changed) {
|
|
2395
|
+
const version = translations[tag];
|
|
2396
|
+
if (version && !removed.has(tag)) {
|
|
2397
|
+
patch[tag] = { subject: storedTemplateLine(version.subject), preheader: storedTemplateLine(version.preheader), document: version.document, status: version.status };
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
for (const tag of removed) {
|
|
2401
|
+
if (Object.prototype.hasOwnProperty.call(loaded.translations, tag)) patch[tag] = null;
|
|
2402
|
+
}
|
|
2403
|
+
if (Object.keys(patch).length > 0) out.translations = patch;
|
|
2404
|
+
return out;
|
|
2405
|
+
}
|
|
2406
|
+
function restorePatch(old, current, workspaceLocales) {
|
|
2407
|
+
const oldMain = canonicalLocale(old.locale) ?? old.locale;
|
|
2408
|
+
const listed = workspaceLocales ? new Set(workspaceLocales.map((t) => canonicalLocale(t) ?? t)) : null;
|
|
2409
|
+
const patch = {};
|
|
2410
|
+
const skipped = [];
|
|
2411
|
+
const untouched = /* @__PURE__ */ new Set();
|
|
2412
|
+
for (const [tag, version] of Object.entries(old.translations)) {
|
|
2413
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2414
|
+
if (canonical === oldMain) continue;
|
|
2415
|
+
if (listed && !listed.has(canonical)) {
|
|
2416
|
+
skipped.push(canonical);
|
|
2417
|
+
untouched.add(canonical);
|
|
2418
|
+
continue;
|
|
2419
|
+
}
|
|
2420
|
+
patch[canonical] = version;
|
|
2421
|
+
}
|
|
2422
|
+
for (const tag of Object.keys(current.translations)) {
|
|
2423
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2424
|
+
if (!(canonical in patch) && !untouched.has(canonical)) patch[canonical] = null;
|
|
2425
|
+
if (canonical === oldMain) patch[canonical] = null;
|
|
2426
|
+
}
|
|
2427
|
+
const update = {
|
|
2428
|
+
...oldMain !== (canonicalLocale(current.locale) ?? current.locale) ? { locale: oldMain } : {},
|
|
2429
|
+
subject: old.subject,
|
|
2430
|
+
preheader: old.preheader,
|
|
2431
|
+
document: old.document,
|
|
2432
|
+
...Object.keys(patch).length > 0 ? { translations: patch } : {}
|
|
2433
|
+
};
|
|
2434
|
+
return { update, skipped: skipped.sort() };
|
|
2435
|
+
}
|
|
2436
|
+
function draftsFromTemplate(template) {
|
|
2437
|
+
const translations = {};
|
|
2438
|
+
for (const [tag, version] of Object.entries(template.translations)) translations[canonicalLocale(tag) ?? tag] = version;
|
|
2439
|
+
return {
|
|
2440
|
+
main: { subject: template.subject ?? "", preheader: template.preheader ?? "", document: template.document },
|
|
2441
|
+
translations,
|
|
2442
|
+
mainDocumentChanged: false,
|
|
2443
|
+
changed: [],
|
|
2444
|
+
removed: []
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
var withChanged = (list2, tag) => list2.includes(tag) ? [...list2] : [...list2, tag];
|
|
2448
|
+
var without = (list2, tag) => list2.filter((t) => t !== tag);
|
|
2449
|
+
function draftDocument(drafts, locale) {
|
|
2450
|
+
if (locale === null) return drafts.main.document;
|
|
2451
|
+
return drafts.translations[locale]?.document;
|
|
2452
|
+
}
|
|
2453
|
+
function addLanguageDraft(drafts, locale) {
|
|
2454
|
+
const tag = canonicalLocale(locale) ?? locale;
|
|
2455
|
+
if (drafts.translations[tag]) return drafts;
|
|
2456
|
+
return {
|
|
2457
|
+
...drafts,
|
|
2458
|
+
translations: {
|
|
2459
|
+
...drafts.translations,
|
|
2460
|
+
[tag]: {
|
|
2461
|
+
subject: drafts.main.subject,
|
|
2462
|
+
preheader: drafts.main.preheader,
|
|
2463
|
+
document: structuredClone(drafts.main.document),
|
|
2464
|
+
status: "draft"
|
|
2465
|
+
}
|
|
2466
|
+
},
|
|
2467
|
+
changed: withChanged(drafts.changed, tag),
|
|
2468
|
+
removed: without(drafts.removed, tag)
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
function removeLanguageDraft(drafts, locale) {
|
|
2472
|
+
const tag = canonicalLocale(locale) ?? locale;
|
|
2473
|
+
const { [tag]: _gone, ...rest } = drafts.translations;
|
|
2474
|
+
return { ...drafts, translations: rest, changed: without(drafts.changed, tag), removed: withChanged(drafts.removed, tag) };
|
|
2475
|
+
}
|
|
2476
|
+
function editLanguageDraft(drafts, locale, edit) {
|
|
2477
|
+
if (locale === null) {
|
|
2478
|
+
return {
|
|
2479
|
+
...drafts,
|
|
2480
|
+
main: { ...drafts.main, ...edit },
|
|
2481
|
+
mainDocumentChanged: drafts.mainDocumentChanged || edit.document !== void 0
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
const version = drafts.translations[locale];
|
|
2485
|
+
if (!version) return drafts;
|
|
2486
|
+
const next2 = { ...version, ...edit };
|
|
2487
|
+
return { ...drafts, translations: { ...drafts.translations, [locale]: next2 }, changed: withChanged(drafts.changed, locale) };
|
|
2488
|
+
}
|
|
2489
|
+
function keepLanguageDocument(drafts, locale, document) {
|
|
2490
|
+
if (locale === null) return { ...drafts, main: { ...drafts.main, document } };
|
|
2491
|
+
const version = drafts.translations[locale];
|
|
2492
|
+
if (!version) return drafts;
|
|
2493
|
+
return { ...drafts, translations: { ...drafts.translations, [locale]: { ...version, document } } };
|
|
2494
|
+
}
|
|
2495
|
+
function setLanguageStatus(drafts, locale, status) {
|
|
2496
|
+
const version = drafts.translations[locale];
|
|
2497
|
+
if (!version || version.status === status) return drafts;
|
|
2498
|
+
return { ...drafts, translations: { ...drafts.translations, [locale]: { ...version, status } }, changed: withChanged(drafts.changed, locale) };
|
|
2499
|
+
}
|
|
2500
|
+
function draftsPatch(drafts, loaded) {
|
|
2501
|
+
return buildTemplatePatch({
|
|
2502
|
+
loaded,
|
|
2503
|
+
main: drafts.main,
|
|
2504
|
+
documentChanged: drafts.mainDocumentChanged,
|
|
2505
|
+
translations: drafts.translations,
|
|
2506
|
+
changed: new Set(drafts.changed),
|
|
2507
|
+
removed: new Set(drafts.removed)
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
function draftsDirty(drafts, loaded) {
|
|
2511
|
+
return Object.keys(draftsPatch(drafts, loaded)).length > 0;
|
|
2512
|
+
}
|
|
2513
|
+
function readyWithoutSubject(drafts) {
|
|
2514
|
+
return Object.entries(drafts.translations).filter(([tag, v]) => drafts.changed.includes(tag) && v.status === "ready" && storedTemplateLine(v.subject) === null).map(([tag]) => tag);
|
|
2515
|
+
}
|
|
2516
|
+
function rebaseDrafts(sent, now, saved) {
|
|
2517
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
2518
|
+
let out = draftsFromTemplate(saved);
|
|
2519
|
+
if (now.main.subject !== sent.main.subject) out = editLanguageDraft(out, null, { subject: now.main.subject });
|
|
2520
|
+
if (now.main.preheader !== sent.main.preheader) out = editLanguageDraft(out, null, { preheader: now.main.preheader });
|
|
2521
|
+
out = same(now.main.document, sent.main.document) ? keepLanguageDocument(out, null, now.main.document) : editLanguageDraft(out, null, { document: now.main.document });
|
|
2522
|
+
const tags = /* @__PURE__ */ new Set([...Object.keys(sent.translations), ...Object.keys(now.translations)]);
|
|
2523
|
+
for (const tag of tags) {
|
|
2524
|
+
const before = sent.translations[tag];
|
|
2525
|
+
const after = now.translations[tag];
|
|
2526
|
+
if (same(before, after)) {
|
|
2527
|
+
if (after) out = keepLanguageDocument(out, tag, after.document);
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
if (!after) {
|
|
2531
|
+
out = removeLanguageDraft(out, tag);
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
out = {
|
|
2535
|
+
...out,
|
|
2536
|
+
translations: { ...out.translations, [tag]: after },
|
|
2537
|
+
changed: withChanged(out.changed, tag),
|
|
2538
|
+
removed: without(out.removed, tag)
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
return out;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2205
2544
|
// src/providers.ts
|
|
2206
2545
|
import { z as z10 } from "zod";
|
|
2207
2546
|
var PROVIDER_KINDS = ["smtp", "resend"];
|
|
@@ -3312,6 +3651,16 @@ var sendingRoutes = {
|
|
|
3312
3651
|
access: "admin",
|
|
3313
3652
|
phase: "S2"
|
|
3314
3653
|
},
|
|
3654
|
+
/** Refused with `conflict` while a mailing or an automation step still uses the topic; subscriptions and suppressions on it go with it. */
|
|
3655
|
+
"topics.delete": {
|
|
3656
|
+
method: "DELETE",
|
|
3657
|
+
path: "/v1/topics/:id",
|
|
3658
|
+
params: IdParams,
|
|
3659
|
+
response: Ok,
|
|
3660
|
+
status: 200,
|
|
3661
|
+
access: "admin",
|
|
3662
|
+
phase: "S2"
|
|
3663
|
+
},
|
|
3315
3664
|
"contacts.upsert": {
|
|
3316
3665
|
method: "POST",
|
|
3317
3666
|
path: "/v1/contacts",
|
|
@@ -3786,6 +4135,15 @@ var platformRoutes = {
|
|
|
3786
4135
|
access: "read",
|
|
3787
4136
|
phase: "S4"
|
|
3788
4137
|
},
|
|
4138
|
+
"mailings.languages": {
|
|
4139
|
+
method: "GET",
|
|
4140
|
+
path: "/v1/mailings/:id/languages",
|
|
4141
|
+
params: IdParams,
|
|
4142
|
+
response: MailingLanguageReport,
|
|
4143
|
+
status: 200,
|
|
4144
|
+
access: "read",
|
|
4145
|
+
phase: "S4"
|
|
4146
|
+
},
|
|
3789
4147
|
"signupForms.list": {
|
|
3790
4148
|
method: "GET",
|
|
3791
4149
|
path: "/v1/signup-forms",
|
|
@@ -4383,6 +4741,7 @@ export {
|
|
|
4383
4741
|
EXPORT_WARNING_COUNT_HEADER,
|
|
4384
4742
|
Email,
|
|
4385
4743
|
EmailStep,
|
|
4744
|
+
EmailStepLanguage,
|
|
4386
4745
|
ErrorBody,
|
|
4387
4746
|
ErrorCodeSchema,
|
|
4388
4747
|
Event,
|
|
@@ -4432,7 +4791,10 @@ export {
|
|
|
4432
4791
|
InviteListQuery,
|
|
4433
4792
|
InviteStatus,
|
|
4434
4793
|
JsonValue,
|
|
4794
|
+
LETTER_RECIPIENT_REFUSAL,
|
|
4435
4795
|
LIST_UNSUBSCRIBE_POST_VALUE,
|
|
4796
|
+
LOCALE_TAG_PATTERN,
|
|
4797
|
+
LocaleTag,
|
|
4436
4798
|
MAILING_ACTIONS,
|
|
4437
4799
|
MAILING_KINDS,
|
|
4438
4800
|
MAILING_STATUSES,
|
|
@@ -4450,18 +4812,22 @@ export {
|
|
|
4450
4812
|
MAX_IMPORT_URL_LENGTH,
|
|
4451
4813
|
MAX_INVITE_TTL_DAYS,
|
|
4452
4814
|
MAX_IN_LIST,
|
|
4815
|
+
MAX_LETTER_RECIPIENTS,
|
|
4453
4816
|
MAX_MERGE_DEFAULTS,
|
|
4454
4817
|
MAX_MERGE_DEFAULT_LENGTH,
|
|
4455
4818
|
MAX_MJML_DEPTH,
|
|
4456
4819
|
MAX_MJML_ELEMENTS,
|
|
4457
4820
|
MAX_MJML_IMPORT_BYTES,
|
|
4458
4821
|
MAX_PAGE_LIMIT,
|
|
4822
|
+
MAX_PREHEADER_LENGTH,
|
|
4459
4823
|
MAX_RECIPIENTS_PER_BATCH,
|
|
4460
4824
|
MAX_RE_ENTRY_COOLDOWN_SECONDS,
|
|
4461
4825
|
MAX_SAVED_SECTIONS,
|
|
4462
4826
|
MAX_SPLIT_BRANCHES,
|
|
4463
4827
|
MAX_STEPS,
|
|
4464
4828
|
MAX_STEP_VISITS,
|
|
4829
|
+
MAX_SUBJECT_LENGTH,
|
|
4830
|
+
MAX_TEMPLATE_TRANSLATIONS,
|
|
4465
4831
|
MAX_TRIGGERS,
|
|
4466
4832
|
MEMBER_ROLES,
|
|
4467
4833
|
MERGE_FIELD_PATTERN,
|
|
@@ -4479,6 +4845,9 @@ export {
|
|
|
4479
4845
|
MailingExportQuery,
|
|
4480
4846
|
MailingFinishedData,
|
|
4481
4847
|
MailingKind,
|
|
4848
|
+
MailingLanguage,
|
|
4849
|
+
MailingLanguageReport,
|
|
4850
|
+
MailingLanguageStatus,
|
|
4482
4851
|
MailingListQuery,
|
|
4483
4852
|
MailingMetadata,
|
|
4484
4853
|
MailingRetryFailedRequest,
|
|
@@ -4579,6 +4948,7 @@ export {
|
|
|
4579
4948
|
SuppressionCreate,
|
|
4580
4949
|
SuppressionListQuery,
|
|
4581
4950
|
SuppressionReason,
|
|
4951
|
+
TEMPLATE_LANGUAGE_STATUSES,
|
|
4582
4952
|
TERMINAL_MAILING_STATUSES,
|
|
4583
4953
|
Tag,
|
|
4584
4954
|
TagAssignment,
|
|
@@ -4594,8 +4964,13 @@ export {
|
|
|
4594
4964
|
TemplateImportPreview,
|
|
4595
4965
|
TemplateImportPreviewRequest,
|
|
4596
4966
|
TemplateImportResult,
|
|
4967
|
+
TemplateLanguageStatus,
|
|
4597
4968
|
TemplateListQuery,
|
|
4969
|
+
TemplatePreheader,
|
|
4970
|
+
TemplateSubject,
|
|
4598
4971
|
TemplateSummary,
|
|
4972
|
+
TemplateTranslation,
|
|
4973
|
+
TemplateTranslations,
|
|
4599
4974
|
TemplateUpdate,
|
|
4600
4975
|
TemplateUpdatedData,
|
|
4601
4976
|
TemplateVersion,
|
|
@@ -4648,14 +5023,22 @@ export {
|
|
|
4648
5023
|
WorkspaceSettings,
|
|
4649
5024
|
WorkspaceUpdate,
|
|
4650
5025
|
acceptsIdempotencyKey,
|
|
5026
|
+
addLanguageDraft,
|
|
4651
5027
|
alwaysWaits,
|
|
4652
5028
|
automationFilterFamily,
|
|
4653
5029
|
automationRoutes,
|
|
4654
5030
|
billingRoutes,
|
|
4655
5031
|
buildPath,
|
|
5032
|
+
buildTemplatePatch,
|
|
4656
5033
|
canTransition,
|
|
5034
|
+
canonicalLocale,
|
|
4657
5035
|
cycles,
|
|
4658
5036
|
defaultSettings,
|
|
5037
|
+
draftDocument,
|
|
5038
|
+
draftsDirty,
|
|
5039
|
+
draftsFromTemplate,
|
|
5040
|
+
draftsPatch,
|
|
5041
|
+
editLanguageDraft,
|
|
4659
5042
|
emptyFlow,
|
|
4660
5043
|
errorBody,
|
|
4661
5044
|
exportFilename,
|
|
@@ -4671,6 +5054,10 @@ export {
|
|
|
4671
5054
|
isDayEntirelyPast,
|
|
4672
5055
|
isPublishable,
|
|
4673
5056
|
isRetryableError,
|
|
5057
|
+
keepLanguageDocument,
|
|
5058
|
+
languageName,
|
|
5059
|
+
languageTabs,
|
|
5060
|
+
matchLocale,
|
|
4674
5061
|
matchRoute,
|
|
4675
5062
|
missingRequiredMergeFields,
|
|
4676
5063
|
nowSeconds,
|
|
@@ -4679,12 +5066,19 @@ export {
|
|
|
4679
5066
|
parseExportWarningsHeader,
|
|
4680
5067
|
parseUsageWarningHeader,
|
|
4681
5068
|
platformRoutes,
|
|
5069
|
+
primaryLanguage,
|
|
4682
5070
|
reachable,
|
|
5071
|
+
readyWithoutSubject,
|
|
5072
|
+
rebaseDrafts,
|
|
5073
|
+
removeLanguageDraft,
|
|
5074
|
+
restorePatch,
|
|
4683
5075
|
routes,
|
|
4684
5076
|
sendingRoutes,
|
|
5077
|
+
setLanguageStatus,
|
|
4685
5078
|
signWebhook,
|
|
4686
5079
|
statusForError,
|
|
4687
5080
|
stepTargets,
|
|
5081
|
+
storedTemplateLine,
|
|
4688
5082
|
stronglyConnected,
|
|
4689
5083
|
templateRoutes,
|
|
4690
5084
|
testGroupSize,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.5.0",
|
|
7
7
|
"description": "The v1 API contract of the mail service: zod schemas and types for every request, response, error and webhook, the route table, and webhook signing helpers",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|