@marlinjai/mail-contract 0.2.0 → 0.3.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.js CHANGED
@@ -49,6 +49,7 @@ __export(index_exports, {
49
49
  ApiKeyScope: () => ApiKeyScope,
50
50
  Asset: () => Asset,
51
51
  AssetContentType: () => AssetContentType,
52
+ AssetHost: () => AssetHost,
52
53
  AssetImport: () => AssetImport,
53
54
  AssetPolicy: () => AssetPolicy,
54
55
  AuditAction: () => AuditAction,
@@ -203,6 +204,7 @@ __export(index_exports, {
203
204
  MAILING_KINDS: () => MAILING_KINDS,
204
205
  MAILING_STATUSES: () => MAILING_STATUSES,
205
206
  MAILING_TRANSITIONS: () => MAILING_TRANSITIONS,
207
+ MAX_ALLOWED_ASSET_HOSTS: () => MAX_ALLOWED_ASSET_HOSTS,
206
208
  MAX_ASSET_BYTES: () => MAX_ASSET_BYTES,
207
209
  MAX_DELAY_SECONDS: () => MAX_DELAY_SECONDS,
208
210
  MAX_DOCUMENT_BYTES: () => MAX_DOCUMENT_BYTES,
@@ -215,6 +217,8 @@ __export(index_exports, {
215
217
  MAX_IMPORT_URL_LENGTH: () => MAX_IMPORT_URL_LENGTH,
216
218
  MAX_INVITE_TTL_DAYS: () => MAX_INVITE_TTL_DAYS,
217
219
  MAX_IN_LIST: () => MAX_IN_LIST,
220
+ MAX_MERGE_DEFAULTS: () => MAX_MERGE_DEFAULTS,
221
+ MAX_MERGE_DEFAULT_LENGTH: () => MAX_MERGE_DEFAULT_LENGTH,
218
222
  MAX_MJML_DEPTH: () => MAX_MJML_DEPTH,
219
223
  MAX_MJML_ELEMENTS: () => MAX_MJML_ELEMENTS,
220
224
  MAX_MJML_IMPORT_BYTES: () => MAX_MJML_IMPORT_BYTES,
@@ -259,6 +263,9 @@ __export(index_exports, {
259
263
  MemberCreate: () => MemberCreate,
260
264
  MemberRole: () => MemberRole,
261
265
  MemberUpdate: () => MemberUpdate,
266
+ MergeDefaultValue: () => MergeDefaultValue,
267
+ MergeDefaults: () => MergeDefaults,
268
+ MergeFieldName: () => MergeFieldName,
262
269
  Message: () => Message,
263
270
  MessageFailedData: () => MessageFailedData,
264
271
  MessageListQuery: () => MessageListQuery,
@@ -267,6 +274,8 @@ __export(index_exports, {
267
274
  MessageSummary: () => MessageSummary,
268
275
  NotifyRecipients: () => NotifyRecipients,
269
276
  NotifyStep: () => NotifyStep,
277
+ ON_BEHALF_OF_HEADER: () => ON_BEHALF_OF_HEADER,
278
+ ON_BEHALF_OF_MAX_LENGTH: () => ON_BEHALF_OF_MAX_LENGTH,
270
279
  OffsetDays: () => OffsetDays,
271
280
  Ok: () => Ok,
272
281
  PLAN_FEATURES: () => PLAN_FEATURES,
@@ -344,6 +353,8 @@ __export(index_exports, {
344
353
  Template: () => Template,
345
354
  TemplateCompileRequest: () => TemplateCompileRequest,
346
355
  TemplateCreate: () => TemplateCreate,
356
+ TemplateCreatedData: () => TemplateCreatedData,
357
+ TemplateDeletedData: () => TemplateDeletedData,
347
358
  TemplateDocument: () => TemplateDocument,
348
359
  TemplateExportQuery: () => TemplateExportQuery,
349
360
  TemplateImport: () => TemplateImport,
@@ -353,6 +364,7 @@ __export(index_exports, {
353
364
  TemplateListQuery: () => TemplateListQuery,
354
365
  TemplateSummary: () => TemplateSummary,
355
366
  TemplateUpdate: () => TemplateUpdate,
367
+ TemplateUpdatedData: () => TemplateUpdatedData,
356
368
  TemplateVersion: () => TemplateVersion,
357
369
  TemplateVersionParams: () => TemplateVersionParams,
358
370
  TimeOfDay: () => TimeOfDay,
@@ -429,6 +441,7 @@ __export(index_exports, {
429
441
  matchRoute: () => matchRoute,
430
442
  missingRequiredMergeFields: () => missingRequiredMergeFields,
431
443
  nowSeconds: () => nowSeconds,
444
+ operationsWithAccess: () => operationsWithAccess,
432
445
  page: () => page,
433
446
  parseExportWarningsHeader: () => parseExportWarningsHeader,
434
447
  parseUsageWarningHeader: () => parseUsageWarningHeader,
@@ -566,6 +579,8 @@ var IDEMPOTENCY_KEY_MAX_LENGTH = 255;
566
579
  var IDEMPOTENCY_KEY_RETENTION_HOURS = 24;
567
580
  var SUBJECT_HEADER = "x-mail-subject";
568
581
  var WORKSPACE_HEADER = "x-mail-workspace";
582
+ var ON_BEHALF_OF_HEADER = "x-mail-on-behalf-of";
583
+ var ON_BEHALF_OF_MAX_LENGTH = 200;
569
584
  var REQUEST_ID_HEADER = "x-request-id";
570
585
  var RETRY_AFTER_HEADER = "retry-after";
571
586
  var API_VERSION_PREFIX = "/v1";
@@ -2358,10 +2373,45 @@ var EventListQuery = PageQuery.extend({
2358
2373
  contact_id: Id.optional()
2359
2374
  });
2360
2375
 
2376
+ // src/unsubscribe.ts
2377
+ var UNSUBSCRIBE_PATH_PREFIX = "/u/";
2378
+ var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
2379
+ var RESERVED_MERGE_FIELDS = {
2380
+ /** From the recipient's merge values, else the contact. Supports a fallback. */
2381
+ first_name: { source: "contact", fallback: true, required_for_broadcast: false },
2382
+ last_name: { source: "contact", fallback: true, required_for_broadcast: false },
2383
+ email: { source: "contact", fallback: false, required_for_broadcast: false },
2384
+ /** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
2385
+ unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
2386
+ };
2387
+ var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
2388
+ function findMergeFields(html) {
2389
+ const uses = [];
2390
+ for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
2391
+ uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
2392
+ }
2393
+ return uses;
2394
+ }
2395
+ function missingRequiredMergeFields(html) {
2396
+ const used = new Set(findMergeFields(html).map((u) => u.name));
2397
+ return Object.keys(RESERVED_MERGE_FIELDS).filter(
2398
+ (name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
2399
+ );
2400
+ }
2401
+
2361
2402
  // src/workspace.ts
2362
2403
  var CompanyId = import_zod8.z.string().min(1).max(64);
2363
2404
  var ASSET_POLICIES = ["any", "service_only"];
2364
2405
  var AssetPolicy = import_zod8.z.enum(ASSET_POLICIES);
2406
+ var MAX_ALLOWED_ASSET_HOSTS = 20;
2407
+ var AssetHost = import_zod8.z.string().trim().toLowerCase().min(1).max(253).regex(/^(?=.{1,253}(?::\d{1,5})?$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+(:\d{1,5})?$/, "a host name such as example.com");
2408
+ var MAX_MERGE_DEFAULTS = 50;
2409
+ var MAX_MERGE_DEFAULT_LENGTH = 2e3;
2410
+ var MergeFieldName = import_zod8.z.string().regex(/^[a-z][a-z0-9_]*$/, "a merge-field name: lowercase letters, digits and underscores, starting with a letter").max(64);
2411
+ var MergeDefaultValue = import_zod8.z.union([import_zod8.z.string().max(MAX_MERGE_DEFAULT_LENGTH), import_zod8.z.number().finite(), import_zod8.z.boolean()]);
2412
+ var MergeDefaults = import_zod8.z.record(MergeFieldName, MergeDefaultValue).refine((m) => Object.keys(m).length <= MAX_MERGE_DEFAULTS, `at most ${MAX_MERGE_DEFAULTS} merge defaults`).refine((m) => !Object.keys(m).some((k) => k in RESERVED_MERGE_FIELDS), {
2413
+ message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
2414
+ });
2365
2415
  var WorkspaceSettings = import_zod8.z.object({
2366
2416
  /** Default language of the hosted unsubscribe page (BCP 47 tag, e.g. "de"). */
2367
2417
  default_locale: import_zod8.z.string().min(2).max(35),
@@ -2371,6 +2421,14 @@ var WorkspaceSettings = import_zod8.z.object({
2371
2421
  tracking_enabled: import_zod8.z.boolean(),
2372
2422
  /** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
2373
2423
  asset_policy: AssetPolicy,
2424
+ /**
2425
+ * Hosts a `service_only` workspace also allows, besides the service's own:
2426
+ * the workspace's website, typically. Empty by default. Ignored under `any`,
2427
+ * which allows everything already.
2428
+ */
2429
+ allowed_asset_hosts: import_zod8.z.array(AssetHost).max(MAX_ALLOWED_ASSET_HOSTS),
2430
+ /** Merge values every mail of the workspace can use (see `MergeDefaults`). Empty by default. */
2431
+ merge_defaults: MergeDefaults,
2374
2432
  /**
2375
2433
  * The time zone an automation reads a wall clock in for a contact who has
2376
2434
  * none of their own (A1). Null falls back to UTC.
@@ -2539,7 +2597,16 @@ var AUDIT_ACTIONS = [
2539
2597
  var AuditAction = import_zod8.z.enum(AUDIT_ACTIONS);
2540
2598
  var AuditActor = import_zod8.z.discriminatedUnion("type", [
2541
2599
  import_zod8.z.object({ type: import_zod8.z.literal("member"), member_id: Id, subject: import_zod8.z.string().min(1) }),
2542
- import_zod8.z.object({ type: import_zod8.z.literal("api_key"), api_key_id: Id }),
2600
+ import_zod8.z.object({
2601
+ type: import_zod8.z.literal("api_key"),
2602
+ api_key_id: Id,
2603
+ /**
2604
+ * Who the client's application said it was acting for, from
2605
+ * `ON_BEHALF_OF_HEADER`: reported by that application, never verified.
2606
+ * Absent when the call carried no label.
2607
+ */
2608
+ on_behalf_of: import_zod8.z.string().min(1).max(ON_BEHALF_OF_MAX_LENGTH).optional()
2609
+ }),
2543
2610
  /** The service itself (the worker, a bounce, the hosted unsubscribe page). */
2544
2611
  import_zod8.z.object({ type: import_zod8.z.literal("system"), reason: import_zod8.z.string().min(1).max(200) })
2545
2612
  ]);
@@ -2867,7 +2934,16 @@ var WEBHOOK_EVENT_TYPES = [
2867
2934
  * `webhook_events.type` is checked against it, not because an endpoint can ask
2868
2935
  * to receive everybody's.
2869
2936
  */
2870
- "automation.webhook"
2937
+ "automation.webhook",
2938
+ /**
2939
+ * E4 of the embeddable-mail plan: a template changed, so a client that
2940
+ * caches or mirrors templates (an app embedding the editor beside the
2941
+ * hosted dashboard) can invalidate. The service stays the source of truth:
2942
+ * the event carries the new version, never the document.
2943
+ */
2944
+ "template.created",
2945
+ "template.updated",
2946
+ "template.deleted"
2871
2947
  ];
2872
2948
  var WebhookEventType = import_zod12.z.enum(WEBHOOK_EVENT_TYPES);
2873
2949
  var MessageEventBase = import_zod12.z.object({
@@ -3029,6 +3105,15 @@ var AutomationWebhookData = AutomationRunEventBase.extend({
3029
3105
  payload: JsonValue.nullable(),
3030
3106
  fired_at: Timestamp
3031
3107
  });
3108
+ var TemplateEventBase = import_zod12.z.object({
3109
+ template_id: Id,
3110
+ name: import_zod12.z.string().min(1).max(200),
3111
+ version: import_zod12.z.number().int().min(1),
3112
+ source: import_zod12.z.enum(["api", "dashboard", "mjml_import"])
3113
+ });
3114
+ var TemplateCreatedData = TemplateEventBase.extend({ created_at: Timestamp });
3115
+ var TemplateUpdatedData = TemplateEventBase.extend({ archived: import_zod12.z.boolean(), updated_at: Timestamp });
3116
+ var TemplateDeletedData = TemplateEventBase.extend({ deleted_at: Timestamp });
3032
3117
  var envelope = (type, data) => import_zod12.z.object({
3033
3118
  /** Unique per event: a receiver deduplicates on it (deliveries may repeat). */
3034
3119
  id: Id,
@@ -3056,7 +3141,10 @@ var WebhookEvent = import_zod12.z.discriminatedUnion("type", [
3056
3141
  envelope("automation.run_completed", AutomationRunCompletedData),
3057
3142
  envelope("automation.run_exited", AutomationRunExitedData),
3058
3143
  envelope("automation.run_failed", AutomationRunFailedData),
3059
- envelope("automation.webhook", AutomationWebhookData)
3144
+ envelope("automation.webhook", AutomationWebhookData),
3145
+ envelope("template.created", TemplateCreatedData),
3146
+ envelope("template.updated", TemplateUpdatedData),
3147
+ envelope("template.deleted", TemplateDeletedData)
3060
3148
  ]);
3061
3149
  var WebhookEndpoint = import_zod12.z.object({
3062
3150
  id: Id,
@@ -3179,32 +3267,6 @@ async function verifyWebhook(input) {
3179
3267
  return matched ? { ok: true, timestamp } : { ok: false, reason: "signature_mismatch" };
3180
3268
  }
3181
3269
 
3182
- // src/unsubscribe.ts
3183
- var UNSUBSCRIBE_PATH_PREFIX = "/u/";
3184
- var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
3185
- var RESERVED_MERGE_FIELDS = {
3186
- /** From the recipient's merge values, else the contact. Supports a fallback. */
3187
- first_name: { source: "contact", fallback: true, required_for_broadcast: false },
3188
- last_name: { source: "contact", fallback: true, required_for_broadcast: false },
3189
- email: { source: "contact", fallback: false, required_for_broadcast: false },
3190
- /** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
3191
- unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
3192
- };
3193
- var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
3194
- function findMergeFields(html) {
3195
- const uses = [];
3196
- for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
3197
- uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
3198
- }
3199
- return uses;
3200
- }
3201
- function missingRequiredMergeFields(html) {
3202
- const used = new Set(findMergeFields(html).map((u) => u.name));
3203
- return Object.keys(RESERVED_MERGE_FIELDS).filter(
3204
- (name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
3205
- );
3206
- }
3207
-
3208
3270
  // src/billing.ts
3209
3271
  var import_zod13 = require("zod");
3210
3272
  var PLAN_IDS = ["free", "starter", "growth", "design_partner"];
@@ -4632,6 +4694,9 @@ function buildPath(path, params = {}) {
4632
4694
  return encodeURIComponent(String(value));
4633
4695
  });
4634
4696
  }
4697
+ function operationsWithAccess(access) {
4698
+ return Object.entries(routes).filter(([, route]) => route.access === access).map(([id]) => id).sort();
4699
+ }
4635
4700
  function matchRoute(method, pathname) {
4636
4701
  for (const [id, route] of Object.entries(routes)) {
4637
4702
  if (route.method !== method) continue;
@@ -4683,6 +4748,7 @@ function matchRoute(method, pathname) {
4683
4748
  ApiKeyScope,
4684
4749
  Asset,
4685
4750
  AssetContentType,
4751
+ AssetHost,
4686
4752
  AssetImport,
4687
4753
  AssetPolicy,
4688
4754
  AuditAction,
@@ -4837,6 +4903,7 @@ function matchRoute(method, pathname) {
4837
4903
  MAILING_KINDS,
4838
4904
  MAILING_STATUSES,
4839
4905
  MAILING_TRANSITIONS,
4906
+ MAX_ALLOWED_ASSET_HOSTS,
4840
4907
  MAX_ASSET_BYTES,
4841
4908
  MAX_DELAY_SECONDS,
4842
4909
  MAX_DOCUMENT_BYTES,
@@ -4849,6 +4916,8 @@ function matchRoute(method, pathname) {
4849
4916
  MAX_IMPORT_URL_LENGTH,
4850
4917
  MAX_INVITE_TTL_DAYS,
4851
4918
  MAX_IN_LIST,
4919
+ MAX_MERGE_DEFAULTS,
4920
+ MAX_MERGE_DEFAULT_LENGTH,
4852
4921
  MAX_MJML_DEPTH,
4853
4922
  MAX_MJML_ELEMENTS,
4854
4923
  MAX_MJML_IMPORT_BYTES,
@@ -4893,6 +4962,9 @@ function matchRoute(method, pathname) {
4893
4962
  MemberCreate,
4894
4963
  MemberRole,
4895
4964
  MemberUpdate,
4965
+ MergeDefaultValue,
4966
+ MergeDefaults,
4967
+ MergeFieldName,
4896
4968
  Message,
4897
4969
  MessageFailedData,
4898
4970
  MessageListQuery,
@@ -4901,6 +4973,8 @@ function matchRoute(method, pathname) {
4901
4973
  MessageSummary,
4902
4974
  NotifyRecipients,
4903
4975
  NotifyStep,
4976
+ ON_BEHALF_OF_HEADER,
4977
+ ON_BEHALF_OF_MAX_LENGTH,
4904
4978
  OffsetDays,
4905
4979
  Ok,
4906
4980
  PLAN_FEATURES,
@@ -4978,6 +5052,8 @@ function matchRoute(method, pathname) {
4978
5052
  Template,
4979
5053
  TemplateCompileRequest,
4980
5054
  TemplateCreate,
5055
+ TemplateCreatedData,
5056
+ TemplateDeletedData,
4981
5057
  TemplateDocument,
4982
5058
  TemplateExportQuery,
4983
5059
  TemplateImport,
@@ -4987,6 +5063,7 @@ function matchRoute(method, pathname) {
4987
5063
  TemplateListQuery,
4988
5064
  TemplateSummary,
4989
5065
  TemplateUpdate,
5066
+ TemplateUpdatedData,
4990
5067
  TemplateVersion,
4991
5068
  TemplateVersionParams,
4992
5069
  TimeOfDay,
@@ -5063,6 +5140,7 @@ function matchRoute(method, pathname) {
5063
5140
  matchRoute,
5064
5141
  missingRequiredMergeFields,
5065
5142
  nowSeconds,
5143
+ operationsWithAccess,
5066
5144
  page,
5067
5145
  parseExportWarningsHeader,
5068
5146
  parseUsageWarningHeader,
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";
@@ -1906,10 +1908,45 @@ var EventListQuery = PageQuery.extend({
1906
1908
  contact_id: Id.optional()
1907
1909
  });
1908
1910
 
1911
+ // src/unsubscribe.ts
1912
+ var UNSUBSCRIBE_PATH_PREFIX = "/u/";
1913
+ var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
1914
+ var RESERVED_MERGE_FIELDS = {
1915
+ /** From the recipient's merge values, else the contact. Supports a fallback. */
1916
+ first_name: { source: "contact", fallback: true, required_for_broadcast: false },
1917
+ last_name: { source: "contact", fallback: true, required_for_broadcast: false },
1918
+ email: { source: "contact", fallback: false, required_for_broadcast: false },
1919
+ /** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
1920
+ unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
1921
+ };
1922
+ var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
1923
+ function findMergeFields(html) {
1924
+ const uses = [];
1925
+ for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
1926
+ uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
1927
+ }
1928
+ return uses;
1929
+ }
1930
+ function missingRequiredMergeFields(html) {
1931
+ const used = new Set(findMergeFields(html).map((u) => u.name));
1932
+ return Object.keys(RESERVED_MERGE_FIELDS).filter(
1933
+ (name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
1934
+ );
1935
+ }
1936
+
1909
1937
  // src/workspace.ts
1910
1938
  var CompanyId = z8.string().min(1).max(64);
1911
1939
  var ASSET_POLICIES = ["any", "service_only"];
1912
1940
  var AssetPolicy = z8.enum(ASSET_POLICIES);
1941
+ var MAX_ALLOWED_ASSET_HOSTS = 20;
1942
+ 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");
1943
+ var MAX_MERGE_DEFAULTS = 50;
1944
+ var MAX_MERGE_DEFAULT_LENGTH = 2e3;
1945
+ 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);
1946
+ var MergeDefaultValue = z8.union([z8.string().max(MAX_MERGE_DEFAULT_LENGTH), z8.number().finite(), z8.boolean()]);
1947
+ 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), {
1948
+ message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
1949
+ });
1913
1950
  var WorkspaceSettings = z8.object({
1914
1951
  /** Default language of the hosted unsubscribe page (BCP 47 tag, e.g. "de"). */
1915
1952
  default_locale: z8.string().min(2).max(35),
@@ -1919,6 +1956,14 @@ var WorkspaceSettings = z8.object({
1919
1956
  tracking_enabled: z8.boolean(),
1920
1957
  /** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
1921
1958
  asset_policy: AssetPolicy,
1959
+ /**
1960
+ * Hosts a `service_only` workspace also allows, besides the service's own:
1961
+ * the workspace's website, typically. Empty by default. Ignored under `any`,
1962
+ * which allows everything already.
1963
+ */
1964
+ allowed_asset_hosts: z8.array(AssetHost).max(MAX_ALLOWED_ASSET_HOSTS),
1965
+ /** Merge values every mail of the workspace can use (see `MergeDefaults`). Empty by default. */
1966
+ merge_defaults: MergeDefaults,
1922
1967
  /**
1923
1968
  * The time zone an automation reads a wall clock in for a contact who has
1924
1969
  * none of their own (A1). Null falls back to UTC.
@@ -2087,7 +2132,16 @@ var AUDIT_ACTIONS = [
2087
2132
  var AuditAction = z8.enum(AUDIT_ACTIONS);
2088
2133
  var AuditActor = z8.discriminatedUnion("type", [
2089
2134
  z8.object({ type: z8.literal("member"), member_id: Id, subject: z8.string().min(1) }),
2090
- z8.object({ type: z8.literal("api_key"), api_key_id: Id }),
2135
+ z8.object({
2136
+ type: z8.literal("api_key"),
2137
+ api_key_id: Id,
2138
+ /**
2139
+ * Who the client's application said it was acting for, from
2140
+ * `ON_BEHALF_OF_HEADER`: reported by that application, never verified.
2141
+ * Absent when the call carried no label.
2142
+ */
2143
+ on_behalf_of: z8.string().min(1).max(ON_BEHALF_OF_MAX_LENGTH).optional()
2144
+ }),
2091
2145
  /** The service itself (the worker, a bounce, the hosted unsubscribe page). */
2092
2146
  z8.object({ type: z8.literal("system"), reason: z8.string().min(1).max(200) })
2093
2147
  ]);
@@ -2415,7 +2469,16 @@ var WEBHOOK_EVENT_TYPES = [
2415
2469
  * `webhook_events.type` is checked against it, not because an endpoint can ask
2416
2470
  * to receive everybody's.
2417
2471
  */
2418
- "automation.webhook"
2472
+ "automation.webhook",
2473
+ /**
2474
+ * E4 of the embeddable-mail plan: a template changed, so a client that
2475
+ * caches or mirrors templates (an app embedding the editor beside the
2476
+ * hosted dashboard) can invalidate. The service stays the source of truth:
2477
+ * the event carries the new version, never the document.
2478
+ */
2479
+ "template.created",
2480
+ "template.updated",
2481
+ "template.deleted"
2419
2482
  ];
2420
2483
  var WebhookEventType = z12.enum(WEBHOOK_EVENT_TYPES);
2421
2484
  var MessageEventBase = z12.object({
@@ -2577,6 +2640,15 @@ var AutomationWebhookData = AutomationRunEventBase.extend({
2577
2640
  payload: JsonValue.nullable(),
2578
2641
  fired_at: Timestamp
2579
2642
  });
2643
+ var TemplateEventBase = z12.object({
2644
+ template_id: Id,
2645
+ name: z12.string().min(1).max(200),
2646
+ version: z12.number().int().min(1),
2647
+ source: z12.enum(["api", "dashboard", "mjml_import"])
2648
+ });
2649
+ var TemplateCreatedData = TemplateEventBase.extend({ created_at: Timestamp });
2650
+ var TemplateUpdatedData = TemplateEventBase.extend({ archived: z12.boolean(), updated_at: Timestamp });
2651
+ var TemplateDeletedData = TemplateEventBase.extend({ deleted_at: Timestamp });
2580
2652
  var envelope = (type, data) => z12.object({
2581
2653
  /** Unique per event: a receiver deduplicates on it (deliveries may repeat). */
2582
2654
  id: Id,
@@ -2604,7 +2676,10 @@ var WebhookEvent = z12.discriminatedUnion("type", [
2604
2676
  envelope("automation.run_completed", AutomationRunCompletedData),
2605
2677
  envelope("automation.run_exited", AutomationRunExitedData),
2606
2678
  envelope("automation.run_failed", AutomationRunFailedData),
2607
- envelope("automation.webhook", AutomationWebhookData)
2679
+ envelope("automation.webhook", AutomationWebhookData),
2680
+ envelope("template.created", TemplateCreatedData),
2681
+ envelope("template.updated", TemplateUpdatedData),
2682
+ envelope("template.deleted", TemplateDeletedData)
2608
2683
  ]);
2609
2684
  var WebhookEndpoint = z12.object({
2610
2685
  id: Id,
@@ -2727,32 +2802,6 @@ async function verifyWebhook(input) {
2727
2802
  return matched ? { ok: true, timestamp } : { ok: false, reason: "signature_mismatch" };
2728
2803
  }
2729
2804
 
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
2805
  // src/billing.ts
2757
2806
  import { z as z13 } from "zod";
2758
2807
  var PLAN_IDS = ["free", "starter", "growth", "design_partner"];
@@ -4180,6 +4229,9 @@ function buildPath(path, params = {}) {
4180
4229
  return encodeURIComponent(String(value));
4181
4230
  });
4182
4231
  }
4232
+ function operationsWithAccess(access) {
4233
+ return Object.entries(routes).filter(([, route]) => route.access === access).map(([id]) => id).sort();
4234
+ }
4183
4235
  function matchRoute(method, pathname) {
4184
4236
  for (const [id, route] of Object.entries(routes)) {
4185
4237
  if (route.method !== method) continue;
@@ -4230,6 +4282,7 @@ export {
4230
4282
  ApiKeyScope,
4231
4283
  Asset,
4232
4284
  AssetContentType,
4285
+ AssetHost,
4233
4286
  AssetImport,
4234
4287
  AssetPolicy,
4235
4288
  AuditAction,
@@ -4384,6 +4437,7 @@ export {
4384
4437
  MAILING_KINDS,
4385
4438
  MAILING_STATUSES,
4386
4439
  MAILING_TRANSITIONS,
4440
+ MAX_ALLOWED_ASSET_HOSTS,
4387
4441
  MAX_ASSET_BYTES,
4388
4442
  MAX_DELAY_SECONDS,
4389
4443
  MAX_DOCUMENT_BYTES,
@@ -4396,6 +4450,8 @@ export {
4396
4450
  MAX_IMPORT_URL_LENGTH,
4397
4451
  MAX_INVITE_TTL_DAYS,
4398
4452
  MAX_IN_LIST,
4453
+ MAX_MERGE_DEFAULTS,
4454
+ MAX_MERGE_DEFAULT_LENGTH,
4399
4455
  MAX_MJML_DEPTH,
4400
4456
  MAX_MJML_ELEMENTS,
4401
4457
  MAX_MJML_IMPORT_BYTES,
@@ -4440,6 +4496,9 @@ export {
4440
4496
  MemberCreate,
4441
4497
  MemberRole,
4442
4498
  MemberUpdate,
4499
+ MergeDefaultValue,
4500
+ MergeDefaults,
4501
+ MergeFieldName,
4443
4502
  Message,
4444
4503
  MessageFailedData,
4445
4504
  MessageListQuery,
@@ -4448,6 +4507,8 @@ export {
4448
4507
  MessageSummary,
4449
4508
  NotifyRecipients,
4450
4509
  NotifyStep,
4510
+ ON_BEHALF_OF_HEADER,
4511
+ ON_BEHALF_OF_MAX_LENGTH,
4451
4512
  OffsetDays,
4452
4513
  Ok,
4453
4514
  PLAN_FEATURES,
@@ -4525,6 +4586,8 @@ export {
4525
4586
  Template,
4526
4587
  TemplateCompileRequest,
4527
4588
  TemplateCreate,
4589
+ TemplateCreatedData,
4590
+ TemplateDeletedData,
4528
4591
  TemplateDocument,
4529
4592
  TemplateExportQuery,
4530
4593
  TemplateImport,
@@ -4534,6 +4597,7 @@ export {
4534
4597
  TemplateListQuery,
4535
4598
  TemplateSummary,
4536
4599
  TemplateUpdate,
4600
+ TemplateUpdatedData,
4537
4601
  TemplateVersion,
4538
4602
  TemplateVersionParams,
4539
4603
  TimeOfDay,
@@ -4610,6 +4674,7 @@ export {
4610
4674
  matchRoute,
4611
4675
  missingRequiredMergeFields,
4612
4676
  nowSeconds,
4677
+ operationsWithAccess,
4613
4678
  page,
4614
4679
  parseExportWarningsHeader,
4615
4680
  parseUsageWarningHeader,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.2.0",
6
+ "version": "0.3.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": {