@marlinjai/mail-contract 0.2.0 → 0.4.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 +900 -259
- package/dist/index.d.ts +900 -259
- package/dist/index.js +139 -32
- package/dist/index.mjs +124 -32
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -114,6 +114,8 @@ var IDEMPOTENCY_KEY_MAX_LENGTH = 255;
|
|
|
114
114
|
var IDEMPOTENCY_KEY_RETENTION_HOURS = 24;
|
|
115
115
|
var SUBJECT_HEADER = "x-mail-subject";
|
|
116
116
|
var WORKSPACE_HEADER = "x-mail-workspace";
|
|
117
|
+
var ON_BEHALF_OF_HEADER = "x-mail-on-behalf-of";
|
|
118
|
+
var ON_BEHALF_OF_MAX_LENGTH = 200;
|
|
117
119
|
var REQUEST_ID_HEADER = "x-request-id";
|
|
118
120
|
var RETRY_AFTER_HEADER = "retry-after";
|
|
119
121
|
var API_VERSION_PREFIX = "/v1";
|
|
@@ -437,7 +439,12 @@ var Mailing = z5.object({
|
|
|
437
439
|
/** The template the document was taken from, if any. The snapshot is what is sent. */
|
|
438
440
|
template_id: Id.nullable(),
|
|
439
441
|
document: TemplateDocument,
|
|
440
|
-
|
|
442
|
+
/**
|
|
443
|
+
* The topic the mailing is sent under. Null for a letter (a one-to-one
|
|
444
|
+
* mailing with at most one recipient, which belongs to no topic and whose
|
|
445
|
+
* unsubscribe link offers every topic) and for an automation's notification.
|
|
446
|
+
*/
|
|
447
|
+
topic: Slug.nullable(),
|
|
441
448
|
provider_id: Id,
|
|
442
449
|
status: MailingStatus,
|
|
443
450
|
counts: MailingCounts,
|
|
@@ -462,7 +469,13 @@ var MailingContent = {
|
|
|
462
469
|
name: z5.string().max(200).optional(),
|
|
463
470
|
subject: z5.string().min(1).max(998),
|
|
464
471
|
preheader: z5.string().max(500).optional(),
|
|
465
|
-
|
|
472
|
+
/**
|
|
473
|
+
* Omitted or null makes the mailing a letter: one recipient at most, sent to
|
|
474
|
+
* anybody not blocked on every topic, with an unsubscribe link that offers
|
|
475
|
+
* every topic and no List-Unsubscribe headers. See "Letters" in the
|
|
476
|
+
* contract's documentation.
|
|
477
|
+
*/
|
|
478
|
+
topic: Slug.nullable().optional(),
|
|
466
479
|
provider_id: Id,
|
|
467
480
|
metadata: MailingMetadata.optional()
|
|
468
481
|
};
|
|
@@ -478,7 +491,8 @@ var MailingUpdate = z5.object({
|
|
|
478
491
|
name: z5.string().max(200).nullable().optional(),
|
|
479
492
|
subject: MailingContent.subject.optional(),
|
|
480
493
|
preheader: z5.string().max(500).nullable().optional(),
|
|
481
|
-
|
|
494
|
+
/** Null turns a draft into a letter, which it can only be with at most one recipient. */
|
|
495
|
+
topic: Slug.nullable().optional(),
|
|
482
496
|
provider_id: Id.optional(),
|
|
483
497
|
metadata: MailingMetadata.optional(),
|
|
484
498
|
document: TemplateDocument.optional()
|
|
@@ -526,6 +540,8 @@ var Recipient = z5.object({
|
|
|
526
540
|
updated_at: Timestamp
|
|
527
541
|
});
|
|
528
542
|
var MAX_RECIPIENTS_PER_BATCH = 1e3;
|
|
543
|
+
var MAX_LETTER_RECIPIENTS = 1;
|
|
544
|
+
var LETTER_RECIPIENT_REFUSAL = "letter_has_one_recipient";
|
|
529
545
|
var RecipientInput = z5.object({
|
|
530
546
|
contact_id: Id.optional(),
|
|
531
547
|
external_id: z5.string().min(1).max(255).optional(),
|
|
@@ -1906,10 +1922,45 @@ var EventListQuery = PageQuery.extend({
|
|
|
1906
1922
|
contact_id: Id.optional()
|
|
1907
1923
|
});
|
|
1908
1924
|
|
|
1925
|
+
// src/unsubscribe.ts
|
|
1926
|
+
var UNSUBSCRIBE_PATH_PREFIX = "/u/";
|
|
1927
|
+
var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
|
|
1928
|
+
var RESERVED_MERGE_FIELDS = {
|
|
1929
|
+
/** From the recipient's merge values, else the contact. Supports a fallback. */
|
|
1930
|
+
first_name: { source: "contact", fallback: true, required_for_broadcast: false },
|
|
1931
|
+
last_name: { source: "contact", fallback: true, required_for_broadcast: false },
|
|
1932
|
+
email: { source: "contact", fallback: false, required_for_broadcast: false },
|
|
1933
|
+
/** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
|
|
1934
|
+
unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
|
|
1935
|
+
};
|
|
1936
|
+
var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
|
|
1937
|
+
function findMergeFields(html) {
|
|
1938
|
+
const uses = [];
|
|
1939
|
+
for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
|
|
1940
|
+
uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
|
|
1941
|
+
}
|
|
1942
|
+
return uses;
|
|
1943
|
+
}
|
|
1944
|
+
function missingRequiredMergeFields(html) {
|
|
1945
|
+
const used = new Set(findMergeFields(html).map((u) => u.name));
|
|
1946
|
+
return Object.keys(RESERVED_MERGE_FIELDS).filter(
|
|
1947
|
+
(name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1909
1951
|
// src/workspace.ts
|
|
1910
1952
|
var CompanyId = z8.string().min(1).max(64);
|
|
1911
1953
|
var ASSET_POLICIES = ["any", "service_only"];
|
|
1912
1954
|
var AssetPolicy = z8.enum(ASSET_POLICIES);
|
|
1955
|
+
var MAX_ALLOWED_ASSET_HOSTS = 20;
|
|
1956
|
+
var AssetHost = z8.string().trim().toLowerCase().min(1).max(253).regex(/^(?=.{1,253}(?::\d{1,5})?$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+(:\d{1,5})?$/, "a host name such as example.com");
|
|
1957
|
+
var MAX_MERGE_DEFAULTS = 50;
|
|
1958
|
+
var MAX_MERGE_DEFAULT_LENGTH = 2e3;
|
|
1959
|
+
var MergeFieldName = z8.string().regex(/^[a-z][a-z0-9_]*$/, "a merge-field name: lowercase letters, digits and underscores, starting with a letter").max(64);
|
|
1960
|
+
var MergeDefaultValue = z8.union([z8.string().max(MAX_MERGE_DEFAULT_LENGTH), z8.number().finite(), z8.boolean()]);
|
|
1961
|
+
var MergeDefaults = z8.record(MergeFieldName, MergeDefaultValue).refine((m) => Object.keys(m).length <= MAX_MERGE_DEFAULTS, `at most ${MAX_MERGE_DEFAULTS} merge defaults`).refine((m) => !Object.keys(m).some((k) => k in RESERVED_MERGE_FIELDS), {
|
|
1962
|
+
message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
|
|
1963
|
+
});
|
|
1913
1964
|
var WorkspaceSettings = z8.object({
|
|
1914
1965
|
/** Default language of the hosted unsubscribe page (BCP 47 tag, e.g. "de"). */
|
|
1915
1966
|
default_locale: z8.string().min(2).max(35),
|
|
@@ -1919,6 +1970,14 @@ var WorkspaceSettings = z8.object({
|
|
|
1919
1970
|
tracking_enabled: z8.boolean(),
|
|
1920
1971
|
/** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
|
|
1921
1972
|
asset_policy: AssetPolicy,
|
|
1973
|
+
/**
|
|
1974
|
+
* Hosts a `service_only` workspace also allows, besides the service's own:
|
|
1975
|
+
* the workspace's website, typically. Empty by default. Ignored under `any`,
|
|
1976
|
+
* which allows everything already.
|
|
1977
|
+
*/
|
|
1978
|
+
allowed_asset_hosts: z8.array(AssetHost).max(MAX_ALLOWED_ASSET_HOSTS),
|
|
1979
|
+
/** Merge values every mail of the workspace can use (see `MergeDefaults`). Empty by default. */
|
|
1980
|
+
merge_defaults: MergeDefaults,
|
|
1922
1981
|
/**
|
|
1923
1982
|
* The time zone an automation reads a wall clock in for a contact who has
|
|
1924
1983
|
* none of their own (A1). Null falls back to UTC.
|
|
@@ -2020,6 +2079,7 @@ var AUDIT_ACTIONS = [
|
|
|
2020
2079
|
"provider.anomaly_cleared",
|
|
2021
2080
|
"topic.created",
|
|
2022
2081
|
"topic.updated",
|
|
2082
|
+
"topic.deleted",
|
|
2023
2083
|
"template.created",
|
|
2024
2084
|
"template.updated",
|
|
2025
2085
|
"template.deleted",
|
|
@@ -2087,7 +2147,16 @@ var AUDIT_ACTIONS = [
|
|
|
2087
2147
|
var AuditAction = z8.enum(AUDIT_ACTIONS);
|
|
2088
2148
|
var AuditActor = z8.discriminatedUnion("type", [
|
|
2089
2149
|
z8.object({ type: z8.literal("member"), member_id: Id, subject: z8.string().min(1) }),
|
|
2090
|
-
z8.object({
|
|
2150
|
+
z8.object({
|
|
2151
|
+
type: z8.literal("api_key"),
|
|
2152
|
+
api_key_id: Id,
|
|
2153
|
+
/**
|
|
2154
|
+
* Who the client's application said it was acting for, from
|
|
2155
|
+
* `ON_BEHALF_OF_HEADER`: reported by that application, never verified.
|
|
2156
|
+
* Absent when the call carried no label.
|
|
2157
|
+
*/
|
|
2158
|
+
on_behalf_of: z8.string().min(1).max(ON_BEHALF_OF_MAX_LENGTH).optional()
|
|
2159
|
+
}),
|
|
2091
2160
|
/** The service itself (the worker, a bounce, the hosted unsubscribe page). */
|
|
2092
2161
|
z8.object({ type: z8.literal("system"), reason: z8.string().min(1).max(200) })
|
|
2093
2162
|
]);
|
|
@@ -2415,7 +2484,16 @@ var WEBHOOK_EVENT_TYPES = [
|
|
|
2415
2484
|
* `webhook_events.type` is checked against it, not because an endpoint can ask
|
|
2416
2485
|
* to receive everybody's.
|
|
2417
2486
|
*/
|
|
2418
|
-
"automation.webhook"
|
|
2487
|
+
"automation.webhook",
|
|
2488
|
+
/**
|
|
2489
|
+
* E4 of the embeddable-mail plan: a template changed, so a client that
|
|
2490
|
+
* caches or mirrors templates (an app embedding the editor beside the
|
|
2491
|
+
* hosted dashboard) can invalidate. The service stays the source of truth:
|
|
2492
|
+
* the event carries the new version, never the document.
|
|
2493
|
+
*/
|
|
2494
|
+
"template.created",
|
|
2495
|
+
"template.updated",
|
|
2496
|
+
"template.deleted"
|
|
2419
2497
|
];
|
|
2420
2498
|
var WebhookEventType = z12.enum(WEBHOOK_EVENT_TYPES);
|
|
2421
2499
|
var MessageEventBase = z12.object({
|
|
@@ -2577,6 +2655,15 @@ var AutomationWebhookData = AutomationRunEventBase.extend({
|
|
|
2577
2655
|
payload: JsonValue.nullable(),
|
|
2578
2656
|
fired_at: Timestamp
|
|
2579
2657
|
});
|
|
2658
|
+
var TemplateEventBase = z12.object({
|
|
2659
|
+
template_id: Id,
|
|
2660
|
+
name: z12.string().min(1).max(200),
|
|
2661
|
+
version: z12.number().int().min(1),
|
|
2662
|
+
source: z12.enum(["api", "dashboard", "mjml_import"])
|
|
2663
|
+
});
|
|
2664
|
+
var TemplateCreatedData = TemplateEventBase.extend({ created_at: Timestamp });
|
|
2665
|
+
var TemplateUpdatedData = TemplateEventBase.extend({ archived: z12.boolean(), updated_at: Timestamp });
|
|
2666
|
+
var TemplateDeletedData = TemplateEventBase.extend({ deleted_at: Timestamp });
|
|
2580
2667
|
var envelope = (type, data) => z12.object({
|
|
2581
2668
|
/** Unique per event: a receiver deduplicates on it (deliveries may repeat). */
|
|
2582
2669
|
id: Id,
|
|
@@ -2604,7 +2691,10 @@ var WebhookEvent = z12.discriminatedUnion("type", [
|
|
|
2604
2691
|
envelope("automation.run_completed", AutomationRunCompletedData),
|
|
2605
2692
|
envelope("automation.run_exited", AutomationRunExitedData),
|
|
2606
2693
|
envelope("automation.run_failed", AutomationRunFailedData),
|
|
2607
|
-
envelope("automation.webhook", AutomationWebhookData)
|
|
2694
|
+
envelope("automation.webhook", AutomationWebhookData),
|
|
2695
|
+
envelope("template.created", TemplateCreatedData),
|
|
2696
|
+
envelope("template.updated", TemplateUpdatedData),
|
|
2697
|
+
envelope("template.deleted", TemplateDeletedData)
|
|
2608
2698
|
]);
|
|
2609
2699
|
var WebhookEndpoint = z12.object({
|
|
2610
2700
|
id: Id,
|
|
@@ -2727,32 +2817,6 @@ async function verifyWebhook(input) {
|
|
|
2727
2817
|
return matched ? { ok: true, timestamp } : { ok: false, reason: "signature_mismatch" };
|
|
2728
2818
|
}
|
|
2729
2819
|
|
|
2730
|
-
// src/unsubscribe.ts
|
|
2731
|
-
var UNSUBSCRIBE_PATH_PREFIX = "/u/";
|
|
2732
|
-
var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
|
|
2733
|
-
var RESERVED_MERGE_FIELDS = {
|
|
2734
|
-
/** From the recipient's merge values, else the contact. Supports a fallback. */
|
|
2735
|
-
first_name: { source: "contact", fallback: true, required_for_broadcast: false },
|
|
2736
|
-
last_name: { source: "contact", fallback: true, required_for_broadcast: false },
|
|
2737
|
-
email: { source: "contact", fallback: false, required_for_broadcast: false },
|
|
2738
|
-
/** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
|
|
2739
|
-
unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
|
|
2740
|
-
};
|
|
2741
|
-
var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
|
|
2742
|
-
function findMergeFields(html) {
|
|
2743
|
-
const uses = [];
|
|
2744
|
-
for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
|
|
2745
|
-
uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
|
|
2746
|
-
}
|
|
2747
|
-
return uses;
|
|
2748
|
-
}
|
|
2749
|
-
function missingRequiredMergeFields(html) {
|
|
2750
|
-
const used = new Set(findMergeFields(html).map((u) => u.name));
|
|
2751
|
-
return Object.keys(RESERVED_MERGE_FIELDS).filter(
|
|
2752
|
-
(name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
|
|
2753
|
-
);
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
2820
|
// src/billing.ts
|
|
2757
2821
|
import { z as z13 } from "zod";
|
|
2758
2822
|
var PLAN_IDS = ["free", "starter", "growth", "design_partner"];
|
|
@@ -3263,6 +3327,16 @@ var sendingRoutes = {
|
|
|
3263
3327
|
access: "admin",
|
|
3264
3328
|
phase: "S2"
|
|
3265
3329
|
},
|
|
3330
|
+
/** Refused with `conflict` while a mailing or an automation step still uses the topic; subscriptions and suppressions on it go with it. */
|
|
3331
|
+
"topics.delete": {
|
|
3332
|
+
method: "DELETE",
|
|
3333
|
+
path: "/v1/topics/:id",
|
|
3334
|
+
params: IdParams,
|
|
3335
|
+
response: Ok,
|
|
3336
|
+
status: 200,
|
|
3337
|
+
access: "admin",
|
|
3338
|
+
phase: "S2"
|
|
3339
|
+
},
|
|
3266
3340
|
"contacts.upsert": {
|
|
3267
3341
|
method: "POST",
|
|
3268
3342
|
path: "/v1/contacts",
|
|
@@ -4180,6 +4254,9 @@ function buildPath(path, params = {}) {
|
|
|
4180
4254
|
return encodeURIComponent(String(value));
|
|
4181
4255
|
});
|
|
4182
4256
|
}
|
|
4257
|
+
function operationsWithAccess(access) {
|
|
4258
|
+
return Object.entries(routes).filter(([, route]) => route.access === access).map(([id]) => id).sort();
|
|
4259
|
+
}
|
|
4183
4260
|
function matchRoute(method, pathname) {
|
|
4184
4261
|
for (const [id, route] of Object.entries(routes)) {
|
|
4185
4262
|
if (route.method !== method) continue;
|
|
@@ -4230,6 +4307,7 @@ export {
|
|
|
4230
4307
|
ApiKeyScope,
|
|
4231
4308
|
Asset,
|
|
4232
4309
|
AssetContentType,
|
|
4310
|
+
AssetHost,
|
|
4233
4311
|
AssetImport,
|
|
4234
4312
|
AssetPolicy,
|
|
4235
4313
|
AuditAction,
|
|
@@ -4379,11 +4457,13 @@ export {
|
|
|
4379
4457
|
InviteListQuery,
|
|
4380
4458
|
InviteStatus,
|
|
4381
4459
|
JsonValue,
|
|
4460
|
+
LETTER_RECIPIENT_REFUSAL,
|
|
4382
4461
|
LIST_UNSUBSCRIBE_POST_VALUE,
|
|
4383
4462
|
MAILING_ACTIONS,
|
|
4384
4463
|
MAILING_KINDS,
|
|
4385
4464
|
MAILING_STATUSES,
|
|
4386
4465
|
MAILING_TRANSITIONS,
|
|
4466
|
+
MAX_ALLOWED_ASSET_HOSTS,
|
|
4387
4467
|
MAX_ASSET_BYTES,
|
|
4388
4468
|
MAX_DELAY_SECONDS,
|
|
4389
4469
|
MAX_DOCUMENT_BYTES,
|
|
@@ -4396,6 +4476,9 @@ export {
|
|
|
4396
4476
|
MAX_IMPORT_URL_LENGTH,
|
|
4397
4477
|
MAX_INVITE_TTL_DAYS,
|
|
4398
4478
|
MAX_IN_LIST,
|
|
4479
|
+
MAX_LETTER_RECIPIENTS,
|
|
4480
|
+
MAX_MERGE_DEFAULTS,
|
|
4481
|
+
MAX_MERGE_DEFAULT_LENGTH,
|
|
4399
4482
|
MAX_MJML_DEPTH,
|
|
4400
4483
|
MAX_MJML_ELEMENTS,
|
|
4401
4484
|
MAX_MJML_IMPORT_BYTES,
|
|
@@ -4440,6 +4523,9 @@ export {
|
|
|
4440
4523
|
MemberCreate,
|
|
4441
4524
|
MemberRole,
|
|
4442
4525
|
MemberUpdate,
|
|
4526
|
+
MergeDefaultValue,
|
|
4527
|
+
MergeDefaults,
|
|
4528
|
+
MergeFieldName,
|
|
4443
4529
|
Message,
|
|
4444
4530
|
MessageFailedData,
|
|
4445
4531
|
MessageListQuery,
|
|
@@ -4448,6 +4534,8 @@ export {
|
|
|
4448
4534
|
MessageSummary,
|
|
4449
4535
|
NotifyRecipients,
|
|
4450
4536
|
NotifyStep,
|
|
4537
|
+
ON_BEHALF_OF_HEADER,
|
|
4538
|
+
ON_BEHALF_OF_MAX_LENGTH,
|
|
4451
4539
|
OffsetDays,
|
|
4452
4540
|
Ok,
|
|
4453
4541
|
PLAN_FEATURES,
|
|
@@ -4525,6 +4613,8 @@ export {
|
|
|
4525
4613
|
Template,
|
|
4526
4614
|
TemplateCompileRequest,
|
|
4527
4615
|
TemplateCreate,
|
|
4616
|
+
TemplateCreatedData,
|
|
4617
|
+
TemplateDeletedData,
|
|
4528
4618
|
TemplateDocument,
|
|
4529
4619
|
TemplateExportQuery,
|
|
4530
4620
|
TemplateImport,
|
|
@@ -4534,6 +4624,7 @@ export {
|
|
|
4534
4624
|
TemplateListQuery,
|
|
4535
4625
|
TemplateSummary,
|
|
4536
4626
|
TemplateUpdate,
|
|
4627
|
+
TemplateUpdatedData,
|
|
4537
4628
|
TemplateVersion,
|
|
4538
4629
|
TemplateVersionParams,
|
|
4539
4630
|
TimeOfDay,
|
|
@@ -4610,6 +4701,7 @@ export {
|
|
|
4610
4701
|
matchRoute,
|
|
4611
4702
|
missingRequiredMergeFields,
|
|
4612
4703
|
nowSeconds,
|
|
4704
|
+
operationsWithAccess,
|
|
4613
4705
|
page,
|
|
4614
4706
|
parseExportWarningsHeader,
|
|
4615
4707
|
parseUsageWarningHeader,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.4.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": {
|