@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.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,
@@ -198,11 +199,13 @@ __export(index_exports, {
198
199
  InviteListQuery: () => InviteListQuery,
199
200
  InviteStatus: () => InviteStatus,
200
201
  JsonValue: () => JsonValue,
202
+ LETTER_RECIPIENT_REFUSAL: () => LETTER_RECIPIENT_REFUSAL,
201
203
  LIST_UNSUBSCRIBE_POST_VALUE: () => LIST_UNSUBSCRIBE_POST_VALUE,
202
204
  MAILING_ACTIONS: () => MAILING_ACTIONS,
203
205
  MAILING_KINDS: () => MAILING_KINDS,
204
206
  MAILING_STATUSES: () => MAILING_STATUSES,
205
207
  MAILING_TRANSITIONS: () => MAILING_TRANSITIONS,
208
+ MAX_ALLOWED_ASSET_HOSTS: () => MAX_ALLOWED_ASSET_HOSTS,
206
209
  MAX_ASSET_BYTES: () => MAX_ASSET_BYTES,
207
210
  MAX_DELAY_SECONDS: () => MAX_DELAY_SECONDS,
208
211
  MAX_DOCUMENT_BYTES: () => MAX_DOCUMENT_BYTES,
@@ -215,6 +218,9 @@ __export(index_exports, {
215
218
  MAX_IMPORT_URL_LENGTH: () => MAX_IMPORT_URL_LENGTH,
216
219
  MAX_INVITE_TTL_DAYS: () => MAX_INVITE_TTL_DAYS,
217
220
  MAX_IN_LIST: () => MAX_IN_LIST,
221
+ MAX_LETTER_RECIPIENTS: () => MAX_LETTER_RECIPIENTS,
222
+ MAX_MERGE_DEFAULTS: () => MAX_MERGE_DEFAULTS,
223
+ MAX_MERGE_DEFAULT_LENGTH: () => MAX_MERGE_DEFAULT_LENGTH,
218
224
  MAX_MJML_DEPTH: () => MAX_MJML_DEPTH,
219
225
  MAX_MJML_ELEMENTS: () => MAX_MJML_ELEMENTS,
220
226
  MAX_MJML_IMPORT_BYTES: () => MAX_MJML_IMPORT_BYTES,
@@ -259,6 +265,9 @@ __export(index_exports, {
259
265
  MemberCreate: () => MemberCreate,
260
266
  MemberRole: () => MemberRole,
261
267
  MemberUpdate: () => MemberUpdate,
268
+ MergeDefaultValue: () => MergeDefaultValue,
269
+ MergeDefaults: () => MergeDefaults,
270
+ MergeFieldName: () => MergeFieldName,
262
271
  Message: () => Message,
263
272
  MessageFailedData: () => MessageFailedData,
264
273
  MessageListQuery: () => MessageListQuery,
@@ -267,6 +276,8 @@ __export(index_exports, {
267
276
  MessageSummary: () => MessageSummary,
268
277
  NotifyRecipients: () => NotifyRecipients,
269
278
  NotifyStep: () => NotifyStep,
279
+ ON_BEHALF_OF_HEADER: () => ON_BEHALF_OF_HEADER,
280
+ ON_BEHALF_OF_MAX_LENGTH: () => ON_BEHALF_OF_MAX_LENGTH,
270
281
  OffsetDays: () => OffsetDays,
271
282
  Ok: () => Ok,
272
283
  PLAN_FEATURES: () => PLAN_FEATURES,
@@ -344,6 +355,8 @@ __export(index_exports, {
344
355
  Template: () => Template,
345
356
  TemplateCompileRequest: () => TemplateCompileRequest,
346
357
  TemplateCreate: () => TemplateCreate,
358
+ TemplateCreatedData: () => TemplateCreatedData,
359
+ TemplateDeletedData: () => TemplateDeletedData,
347
360
  TemplateDocument: () => TemplateDocument,
348
361
  TemplateExportQuery: () => TemplateExportQuery,
349
362
  TemplateImport: () => TemplateImport,
@@ -353,6 +366,7 @@ __export(index_exports, {
353
366
  TemplateListQuery: () => TemplateListQuery,
354
367
  TemplateSummary: () => TemplateSummary,
355
368
  TemplateUpdate: () => TemplateUpdate,
369
+ TemplateUpdatedData: () => TemplateUpdatedData,
356
370
  TemplateVersion: () => TemplateVersion,
357
371
  TemplateVersionParams: () => TemplateVersionParams,
358
372
  TimeOfDay: () => TimeOfDay,
@@ -429,6 +443,7 @@ __export(index_exports, {
429
443
  matchRoute: () => matchRoute,
430
444
  missingRequiredMergeFields: () => missingRequiredMergeFields,
431
445
  nowSeconds: () => nowSeconds,
446
+ operationsWithAccess: () => operationsWithAccess,
432
447
  page: () => page,
433
448
  parseExportWarningsHeader: () => parseExportWarningsHeader,
434
449
  parseUsageWarningHeader: () => parseUsageWarningHeader,
@@ -566,6 +581,8 @@ var IDEMPOTENCY_KEY_MAX_LENGTH = 255;
566
581
  var IDEMPOTENCY_KEY_RETENTION_HOURS = 24;
567
582
  var SUBJECT_HEADER = "x-mail-subject";
568
583
  var WORKSPACE_HEADER = "x-mail-workspace";
584
+ var ON_BEHALF_OF_HEADER = "x-mail-on-behalf-of";
585
+ var ON_BEHALF_OF_MAX_LENGTH = 200;
569
586
  var REQUEST_ID_HEADER = "x-request-id";
570
587
  var RETRY_AFTER_HEADER = "retry-after";
571
588
  var API_VERSION_PREFIX = "/v1";
@@ -889,7 +906,12 @@ var Mailing = import_zod5.z.object({
889
906
  /** The template the document was taken from, if any. The snapshot is what is sent. */
890
907
  template_id: Id.nullable(),
891
908
  document: TemplateDocument,
892
- topic: Slug,
909
+ /**
910
+ * The topic the mailing is sent under. Null for a letter (a one-to-one
911
+ * mailing with at most one recipient, which belongs to no topic and whose
912
+ * unsubscribe link offers every topic) and for an automation's notification.
913
+ */
914
+ topic: Slug.nullable(),
893
915
  provider_id: Id,
894
916
  status: MailingStatus,
895
917
  counts: MailingCounts,
@@ -914,7 +936,13 @@ var MailingContent = {
914
936
  name: import_zod5.z.string().max(200).optional(),
915
937
  subject: import_zod5.z.string().min(1).max(998),
916
938
  preheader: import_zod5.z.string().max(500).optional(),
917
- topic: Slug,
939
+ /**
940
+ * Omitted or null makes the mailing a letter: one recipient at most, sent to
941
+ * anybody not blocked on every topic, with an unsubscribe link that offers
942
+ * every topic and no List-Unsubscribe headers. See "Letters" in the
943
+ * contract's documentation.
944
+ */
945
+ topic: Slug.nullable().optional(),
918
946
  provider_id: Id,
919
947
  metadata: MailingMetadata.optional()
920
948
  };
@@ -930,7 +958,8 @@ var MailingUpdate = import_zod5.z.object({
930
958
  name: import_zod5.z.string().max(200).nullable().optional(),
931
959
  subject: MailingContent.subject.optional(),
932
960
  preheader: import_zod5.z.string().max(500).nullable().optional(),
933
- topic: Slug.optional(),
961
+ /** Null turns a draft into a letter, which it can only be with at most one recipient. */
962
+ topic: Slug.nullable().optional(),
934
963
  provider_id: Id.optional(),
935
964
  metadata: MailingMetadata.optional(),
936
965
  document: TemplateDocument.optional()
@@ -978,6 +1007,8 @@ var Recipient = import_zod5.z.object({
978
1007
  updated_at: Timestamp
979
1008
  });
980
1009
  var MAX_RECIPIENTS_PER_BATCH = 1e3;
1010
+ var MAX_LETTER_RECIPIENTS = 1;
1011
+ var LETTER_RECIPIENT_REFUSAL = "letter_has_one_recipient";
981
1012
  var RecipientInput = import_zod5.z.object({
982
1013
  contact_id: Id.optional(),
983
1014
  external_id: import_zod5.z.string().min(1).max(255).optional(),
@@ -2358,10 +2389,45 @@ var EventListQuery = PageQuery.extend({
2358
2389
  contact_id: Id.optional()
2359
2390
  });
2360
2391
 
2392
+ // src/unsubscribe.ts
2393
+ var UNSUBSCRIBE_PATH_PREFIX = "/u/";
2394
+ var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
2395
+ var RESERVED_MERGE_FIELDS = {
2396
+ /** From the recipient's merge values, else the contact. Supports a fallback. */
2397
+ first_name: { source: "contact", fallback: true, required_for_broadcast: false },
2398
+ last_name: { source: "contact", fallback: true, required_for_broadcast: false },
2399
+ email: { source: "contact", fallback: false, required_for_broadcast: false },
2400
+ /** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
2401
+ unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
2402
+ };
2403
+ var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
2404
+ function findMergeFields(html) {
2405
+ const uses = [];
2406
+ for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
2407
+ uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
2408
+ }
2409
+ return uses;
2410
+ }
2411
+ function missingRequiredMergeFields(html) {
2412
+ const used = new Set(findMergeFields(html).map((u) => u.name));
2413
+ return Object.keys(RESERVED_MERGE_FIELDS).filter(
2414
+ (name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
2415
+ );
2416
+ }
2417
+
2361
2418
  // src/workspace.ts
2362
2419
  var CompanyId = import_zod8.z.string().min(1).max(64);
2363
2420
  var ASSET_POLICIES = ["any", "service_only"];
2364
2421
  var AssetPolicy = import_zod8.z.enum(ASSET_POLICIES);
2422
+ var MAX_ALLOWED_ASSET_HOSTS = 20;
2423
+ 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");
2424
+ var MAX_MERGE_DEFAULTS = 50;
2425
+ var MAX_MERGE_DEFAULT_LENGTH = 2e3;
2426
+ 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);
2427
+ var MergeDefaultValue = import_zod8.z.union([import_zod8.z.string().max(MAX_MERGE_DEFAULT_LENGTH), import_zod8.z.number().finite(), import_zod8.z.boolean()]);
2428
+ 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), {
2429
+ message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
2430
+ });
2365
2431
  var WorkspaceSettings = import_zod8.z.object({
2366
2432
  /** Default language of the hosted unsubscribe page (BCP 47 tag, e.g. "de"). */
2367
2433
  default_locale: import_zod8.z.string().min(2).max(35),
@@ -2371,6 +2437,14 @@ var WorkspaceSettings = import_zod8.z.object({
2371
2437
  tracking_enabled: import_zod8.z.boolean(),
2372
2438
  /** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
2373
2439
  asset_policy: AssetPolicy,
2440
+ /**
2441
+ * Hosts a `service_only` workspace also allows, besides the service's own:
2442
+ * the workspace's website, typically. Empty by default. Ignored under `any`,
2443
+ * which allows everything already.
2444
+ */
2445
+ allowed_asset_hosts: import_zod8.z.array(AssetHost).max(MAX_ALLOWED_ASSET_HOSTS),
2446
+ /** Merge values every mail of the workspace can use (see `MergeDefaults`). Empty by default. */
2447
+ merge_defaults: MergeDefaults,
2374
2448
  /**
2375
2449
  * The time zone an automation reads a wall clock in for a contact who has
2376
2450
  * none of their own (A1). Null falls back to UTC.
@@ -2472,6 +2546,7 @@ var AUDIT_ACTIONS = [
2472
2546
  "provider.anomaly_cleared",
2473
2547
  "topic.created",
2474
2548
  "topic.updated",
2549
+ "topic.deleted",
2475
2550
  "template.created",
2476
2551
  "template.updated",
2477
2552
  "template.deleted",
@@ -2539,7 +2614,16 @@ var AUDIT_ACTIONS = [
2539
2614
  var AuditAction = import_zod8.z.enum(AUDIT_ACTIONS);
2540
2615
  var AuditActor = import_zod8.z.discriminatedUnion("type", [
2541
2616
  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 }),
2617
+ import_zod8.z.object({
2618
+ type: import_zod8.z.literal("api_key"),
2619
+ api_key_id: Id,
2620
+ /**
2621
+ * Who the client's application said it was acting for, from
2622
+ * `ON_BEHALF_OF_HEADER`: reported by that application, never verified.
2623
+ * Absent when the call carried no label.
2624
+ */
2625
+ on_behalf_of: import_zod8.z.string().min(1).max(ON_BEHALF_OF_MAX_LENGTH).optional()
2626
+ }),
2543
2627
  /** The service itself (the worker, a bounce, the hosted unsubscribe page). */
2544
2628
  import_zod8.z.object({ type: import_zod8.z.literal("system"), reason: import_zod8.z.string().min(1).max(200) })
2545
2629
  ]);
@@ -2867,7 +2951,16 @@ var WEBHOOK_EVENT_TYPES = [
2867
2951
  * `webhook_events.type` is checked against it, not because an endpoint can ask
2868
2952
  * to receive everybody's.
2869
2953
  */
2870
- "automation.webhook"
2954
+ "automation.webhook",
2955
+ /**
2956
+ * E4 of the embeddable-mail plan: a template changed, so a client that
2957
+ * caches or mirrors templates (an app embedding the editor beside the
2958
+ * hosted dashboard) can invalidate. The service stays the source of truth:
2959
+ * the event carries the new version, never the document.
2960
+ */
2961
+ "template.created",
2962
+ "template.updated",
2963
+ "template.deleted"
2871
2964
  ];
2872
2965
  var WebhookEventType = import_zod12.z.enum(WEBHOOK_EVENT_TYPES);
2873
2966
  var MessageEventBase = import_zod12.z.object({
@@ -3029,6 +3122,15 @@ var AutomationWebhookData = AutomationRunEventBase.extend({
3029
3122
  payload: JsonValue.nullable(),
3030
3123
  fired_at: Timestamp
3031
3124
  });
3125
+ var TemplateEventBase = import_zod12.z.object({
3126
+ template_id: Id,
3127
+ name: import_zod12.z.string().min(1).max(200),
3128
+ version: import_zod12.z.number().int().min(1),
3129
+ source: import_zod12.z.enum(["api", "dashboard", "mjml_import"])
3130
+ });
3131
+ var TemplateCreatedData = TemplateEventBase.extend({ created_at: Timestamp });
3132
+ var TemplateUpdatedData = TemplateEventBase.extend({ archived: import_zod12.z.boolean(), updated_at: Timestamp });
3133
+ var TemplateDeletedData = TemplateEventBase.extend({ deleted_at: Timestamp });
3032
3134
  var envelope = (type, data) => import_zod12.z.object({
3033
3135
  /** Unique per event: a receiver deduplicates on it (deliveries may repeat). */
3034
3136
  id: Id,
@@ -3056,7 +3158,10 @@ var WebhookEvent = import_zod12.z.discriminatedUnion("type", [
3056
3158
  envelope("automation.run_completed", AutomationRunCompletedData),
3057
3159
  envelope("automation.run_exited", AutomationRunExitedData),
3058
3160
  envelope("automation.run_failed", AutomationRunFailedData),
3059
- envelope("automation.webhook", AutomationWebhookData)
3161
+ envelope("automation.webhook", AutomationWebhookData),
3162
+ envelope("template.created", TemplateCreatedData),
3163
+ envelope("template.updated", TemplateUpdatedData),
3164
+ envelope("template.deleted", TemplateDeletedData)
3060
3165
  ]);
3061
3166
  var WebhookEndpoint = import_zod12.z.object({
3062
3167
  id: Id,
@@ -3179,32 +3284,6 @@ async function verifyWebhook(input) {
3179
3284
  return matched ? { ok: true, timestamp } : { ok: false, reason: "signature_mismatch" };
3180
3285
  }
3181
3286
 
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
3287
  // src/billing.ts
3209
3288
  var import_zod13 = require("zod");
3210
3289
  var PLAN_IDS = ["free", "starter", "growth", "design_partner"];
@@ -3715,6 +3794,16 @@ var sendingRoutes = {
3715
3794
  access: "admin",
3716
3795
  phase: "S2"
3717
3796
  },
3797
+ /** Refused with `conflict` while a mailing or an automation step still uses the topic; subscriptions and suppressions on it go with it. */
3798
+ "topics.delete": {
3799
+ method: "DELETE",
3800
+ path: "/v1/topics/:id",
3801
+ params: IdParams,
3802
+ response: Ok,
3803
+ status: 200,
3804
+ access: "admin",
3805
+ phase: "S2"
3806
+ },
3718
3807
  "contacts.upsert": {
3719
3808
  method: "POST",
3720
3809
  path: "/v1/contacts",
@@ -4632,6 +4721,9 @@ function buildPath(path, params = {}) {
4632
4721
  return encodeURIComponent(String(value));
4633
4722
  });
4634
4723
  }
4724
+ function operationsWithAccess(access) {
4725
+ return Object.entries(routes).filter(([, route]) => route.access === access).map(([id]) => id).sort();
4726
+ }
4635
4727
  function matchRoute(method, pathname) {
4636
4728
  for (const [id, route] of Object.entries(routes)) {
4637
4729
  if (route.method !== method) continue;
@@ -4683,6 +4775,7 @@ function matchRoute(method, pathname) {
4683
4775
  ApiKeyScope,
4684
4776
  Asset,
4685
4777
  AssetContentType,
4778
+ AssetHost,
4686
4779
  AssetImport,
4687
4780
  AssetPolicy,
4688
4781
  AuditAction,
@@ -4832,11 +4925,13 @@ function matchRoute(method, pathname) {
4832
4925
  InviteListQuery,
4833
4926
  InviteStatus,
4834
4927
  JsonValue,
4928
+ LETTER_RECIPIENT_REFUSAL,
4835
4929
  LIST_UNSUBSCRIBE_POST_VALUE,
4836
4930
  MAILING_ACTIONS,
4837
4931
  MAILING_KINDS,
4838
4932
  MAILING_STATUSES,
4839
4933
  MAILING_TRANSITIONS,
4934
+ MAX_ALLOWED_ASSET_HOSTS,
4840
4935
  MAX_ASSET_BYTES,
4841
4936
  MAX_DELAY_SECONDS,
4842
4937
  MAX_DOCUMENT_BYTES,
@@ -4849,6 +4944,9 @@ function matchRoute(method, pathname) {
4849
4944
  MAX_IMPORT_URL_LENGTH,
4850
4945
  MAX_INVITE_TTL_DAYS,
4851
4946
  MAX_IN_LIST,
4947
+ MAX_LETTER_RECIPIENTS,
4948
+ MAX_MERGE_DEFAULTS,
4949
+ MAX_MERGE_DEFAULT_LENGTH,
4852
4950
  MAX_MJML_DEPTH,
4853
4951
  MAX_MJML_ELEMENTS,
4854
4952
  MAX_MJML_IMPORT_BYTES,
@@ -4893,6 +4991,9 @@ function matchRoute(method, pathname) {
4893
4991
  MemberCreate,
4894
4992
  MemberRole,
4895
4993
  MemberUpdate,
4994
+ MergeDefaultValue,
4995
+ MergeDefaults,
4996
+ MergeFieldName,
4896
4997
  Message,
4897
4998
  MessageFailedData,
4898
4999
  MessageListQuery,
@@ -4901,6 +5002,8 @@ function matchRoute(method, pathname) {
4901
5002
  MessageSummary,
4902
5003
  NotifyRecipients,
4903
5004
  NotifyStep,
5005
+ ON_BEHALF_OF_HEADER,
5006
+ ON_BEHALF_OF_MAX_LENGTH,
4904
5007
  OffsetDays,
4905
5008
  Ok,
4906
5009
  PLAN_FEATURES,
@@ -4978,6 +5081,8 @@ function matchRoute(method, pathname) {
4978
5081
  Template,
4979
5082
  TemplateCompileRequest,
4980
5083
  TemplateCreate,
5084
+ TemplateCreatedData,
5085
+ TemplateDeletedData,
4981
5086
  TemplateDocument,
4982
5087
  TemplateExportQuery,
4983
5088
  TemplateImport,
@@ -4987,6 +5092,7 @@ function matchRoute(method, pathname) {
4987
5092
  TemplateListQuery,
4988
5093
  TemplateSummary,
4989
5094
  TemplateUpdate,
5095
+ TemplateUpdatedData,
4990
5096
  TemplateVersion,
4991
5097
  TemplateVersionParams,
4992
5098
  TimeOfDay,
@@ -5063,6 +5169,7 @@ function matchRoute(method, pathname) {
5063
5169
  matchRoute,
5064
5170
  missingRequiredMergeFields,
5065
5171
  nowSeconds,
5172
+ operationsWithAccess,
5066
5173
  page,
5067
5174
  parseExportWarningsHeader,
5068
5175
  parseUsageWarningHeader,