@primitivedotdev/sdk 0.7.0 → 0.9.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/README.md +114 -210
- package/dist/address-parser-BYn8oW5r.js +111 -0
- package/dist/api/generated/index.js +1 -1
- package/dist/api/generated/sdk.gen.js +17 -0
- package/dist/api/index.d.ts +2 -1877
- package/dist/api/index.js +255 -0
- package/dist/api-COSr-Fqm.js +1311 -0
- package/dist/chunk-pbuEa-1d.js +13 -0
- package/dist/contract/index.d.ts +6 -8
- package/dist/contract/index.js +28 -15
- package/dist/{index-DLmAI4UQ.d.ts → index-CbEivn3S.d.ts} +13 -30
- package/dist/index-DVow4Fjd.d.ts +2140 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +10 -3
- package/dist/oclif/api-command.js +89 -1
- package/dist/openapi/index.d.ts +8 -3
- package/dist/openapi/openapi.generated.js +412 -1
- package/dist/openapi/operations.generated.js +255 -0
- package/dist/parser/address-parser.js +129 -0
- package/dist/parser/index.d.ts +4 -19
- package/dist/parser/index.js +7 -122
- package/dist/received-email-D6tKtWwW.js +69 -0
- package/dist/received-email-DNjpq_Wt.d.ts +37 -0
- package/dist/{types-CKFmgitP.d.ts → types-9vXGZjPd.d.ts} +3 -19
- package/dist/types.generated.js +7 -0
- package/dist/types.js +53 -0
- package/dist/webhook/index.d.ts +4 -3
- package/dist/webhook/index.js +3 -3
- package/dist/webhook/received-email.js +82 -0
- package/dist/{webhook-COe5N_Uj.js → webhook-zkN4wUTs.js} +119 -81
- package/oclif.manifest.json +54 -8
- package/package.json +5 -2
- package/dist/chunk-Cl8Af3a2.js +0 -11
|
@@ -16,6 +16,7 @@ export const operationManifest = [
|
|
|
16
16
|
"path": "/account",
|
|
17
17
|
"pathParams": [],
|
|
18
18
|
"queryParams": [],
|
|
19
|
+
"requestSchema": null,
|
|
19
20
|
"sdkName": "getAccount",
|
|
20
21
|
"summary": "Get account info",
|
|
21
22
|
"tag": "Account",
|
|
@@ -32,6 +33,7 @@ export const operationManifest = [
|
|
|
32
33
|
"path": "/account/storage",
|
|
33
34
|
"pathParams": [],
|
|
34
35
|
"queryParams": [],
|
|
36
|
+
"requestSchema": null,
|
|
35
37
|
"sdkName": "getStorageStats",
|
|
36
38
|
"summary": "Get storage usage",
|
|
37
39
|
"tag": "Account",
|
|
@@ -48,6 +50,7 @@ export const operationManifest = [
|
|
|
48
50
|
"path": "/account/webhook-secret",
|
|
49
51
|
"pathParams": [],
|
|
50
52
|
"queryParams": [],
|
|
53
|
+
"requestSchema": null,
|
|
51
54
|
"sdkName": "getWebhookSecret",
|
|
52
55
|
"summary": "Get webhook signing secret",
|
|
53
56
|
"tag": "Account",
|
|
@@ -64,6 +67,7 @@ export const operationManifest = [
|
|
|
64
67
|
"path": "/account/webhook-secret/rotate",
|
|
65
68
|
"pathParams": [],
|
|
66
69
|
"queryParams": [],
|
|
70
|
+
"requestSchema": null,
|
|
67
71
|
"sdkName": "rotateWebhookSecret",
|
|
68
72
|
"summary": "Rotate webhook signing secret",
|
|
69
73
|
"tag": "Account",
|
|
@@ -80,6 +84,26 @@ export const operationManifest = [
|
|
|
80
84
|
"path": "/account",
|
|
81
85
|
"pathParams": [],
|
|
82
86
|
"queryParams": [],
|
|
87
|
+
"requestSchema": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"additionalProperties": false,
|
|
90
|
+
"properties": {
|
|
91
|
+
"spam_threshold": {
|
|
92
|
+
"type": [
|
|
93
|
+
"number",
|
|
94
|
+
"null"
|
|
95
|
+
],
|
|
96
|
+
"minimum": 0,
|
|
97
|
+
"maximum": 15,
|
|
98
|
+
"description": "Global spam score threshold (0-15). Emails scoring above this are rejected. Set to null to disable."
|
|
99
|
+
},
|
|
100
|
+
"discard_content_on_webhook_confirmed": {
|
|
101
|
+
"type": "boolean",
|
|
102
|
+
"description": "Whether to discard email content after the webhook endpoint confirms receipt."
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"minProperties": 1
|
|
106
|
+
},
|
|
83
107
|
"sdkName": "updateAccount",
|
|
84
108
|
"summary": "Update account settings",
|
|
85
109
|
"tag": "Account",
|
|
@@ -96,6 +120,21 @@ export const operationManifest = [
|
|
|
96
120
|
"path": "/domains",
|
|
97
121
|
"pathParams": [],
|
|
98
122
|
"queryParams": [],
|
|
123
|
+
"requestSchema": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"additionalProperties": false,
|
|
126
|
+
"properties": {
|
|
127
|
+
"domain": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"minLength": 1,
|
|
130
|
+
"maxLength": 253,
|
|
131
|
+
"description": "The domain name to claim (e.g. \"example.com\")"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"required": [
|
|
135
|
+
"domain"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
99
138
|
"sdkName": "addDomain",
|
|
100
139
|
"summary": "Claim a new domain",
|
|
101
140
|
"tag": "Domains",
|
|
@@ -120,6 +159,7 @@ export const operationManifest = [
|
|
|
120
159
|
}
|
|
121
160
|
],
|
|
122
161
|
"queryParams": [],
|
|
162
|
+
"requestSchema": null,
|
|
123
163
|
"sdkName": "deleteDomain",
|
|
124
164
|
"summary": "Delete a domain",
|
|
125
165
|
"tag": "Domains",
|
|
@@ -136,6 +176,7 @@ export const operationManifest = [
|
|
|
136
176
|
"path": "/domains",
|
|
137
177
|
"pathParams": [],
|
|
138
178
|
"queryParams": [],
|
|
179
|
+
"requestSchema": null,
|
|
139
180
|
"sdkName": "listDomains",
|
|
140
181
|
"summary": "List all domains",
|
|
141
182
|
"tag": "Domains",
|
|
@@ -160,6 +201,26 @@ export const operationManifest = [
|
|
|
160
201
|
}
|
|
161
202
|
],
|
|
162
203
|
"queryParams": [],
|
|
204
|
+
"requestSchema": {
|
|
205
|
+
"type": "object",
|
|
206
|
+
"additionalProperties": false,
|
|
207
|
+
"properties": {
|
|
208
|
+
"is_active": {
|
|
209
|
+
"type": "boolean",
|
|
210
|
+
"description": "Whether the domain accepts incoming emails"
|
|
211
|
+
},
|
|
212
|
+
"spam_threshold": {
|
|
213
|
+
"type": [
|
|
214
|
+
"number",
|
|
215
|
+
"null"
|
|
216
|
+
],
|
|
217
|
+
"minimum": 0,
|
|
218
|
+
"maximum": 15,
|
|
219
|
+
"description": "Per-domain spam threshold override (Pro plan required)"
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"minProperties": 1
|
|
223
|
+
},
|
|
163
224
|
"sdkName": "updateDomain",
|
|
164
225
|
"summary": "Update domain settings",
|
|
165
226
|
"tag": "Domains",
|
|
@@ -184,6 +245,7 @@ export const operationManifest = [
|
|
|
184
245
|
}
|
|
185
246
|
],
|
|
186
247
|
"queryParams": [],
|
|
248
|
+
"requestSchema": null,
|
|
187
249
|
"sdkName": "verifyDomain",
|
|
188
250
|
"summary": "Verify domain ownership",
|
|
189
251
|
"tag": "Domains",
|
|
@@ -208,6 +270,7 @@ export const operationManifest = [
|
|
|
208
270
|
}
|
|
209
271
|
],
|
|
210
272
|
"queryParams": [],
|
|
273
|
+
"requestSchema": null,
|
|
211
274
|
"sdkName": "deleteEmail",
|
|
212
275
|
"summary": "Delete an email",
|
|
213
276
|
"tag": "Emails",
|
|
@@ -240,6 +303,7 @@ export const operationManifest = [
|
|
|
240
303
|
"type": "string"
|
|
241
304
|
}
|
|
242
305
|
],
|
|
306
|
+
"requestSchema": null,
|
|
243
307
|
"sdkName": "downloadAttachments",
|
|
244
308
|
"summary": "Download email attachments",
|
|
245
309
|
"tag": "Emails",
|
|
@@ -272,6 +336,7 @@ export const operationManifest = [
|
|
|
272
336
|
"type": "string"
|
|
273
337
|
}
|
|
274
338
|
],
|
|
339
|
+
"requestSchema": null,
|
|
275
340
|
"sdkName": "downloadRawEmail",
|
|
276
341
|
"summary": "Download raw email",
|
|
277
342
|
"tag": "Emails",
|
|
@@ -296,6 +361,7 @@ export const operationManifest = [
|
|
|
296
361
|
}
|
|
297
362
|
],
|
|
298
363
|
"queryParams": [],
|
|
364
|
+
"requestSchema": null,
|
|
299
365
|
"sdkName": "getEmail",
|
|
300
366
|
"summary": "Get email details",
|
|
301
367
|
"tag": "Emails",
|
|
@@ -367,6 +433,7 @@ export const operationManifest = [
|
|
|
367
433
|
"type": "string"
|
|
368
434
|
}
|
|
369
435
|
],
|
|
436
|
+
"requestSchema": null,
|
|
370
437
|
"sdkName": "listEmails",
|
|
371
438
|
"summary": "List emails",
|
|
372
439
|
"tag": "Emails",
|
|
@@ -391,6 +458,7 @@ export const operationManifest = [
|
|
|
391
458
|
}
|
|
392
459
|
],
|
|
393
460
|
"queryParams": [],
|
|
461
|
+
"requestSchema": null,
|
|
394
462
|
"sdkName": "replayEmailWebhooks",
|
|
395
463
|
"summary": "Replay email webhooks",
|
|
396
464
|
"tag": "Emails",
|
|
@@ -407,6 +475,37 @@ export const operationManifest = [
|
|
|
407
475
|
"path": "/endpoints",
|
|
408
476
|
"pathParams": [],
|
|
409
477
|
"queryParams": [],
|
|
478
|
+
"requestSchema": {
|
|
479
|
+
"type": "object",
|
|
480
|
+
"additionalProperties": false,
|
|
481
|
+
"properties": {
|
|
482
|
+
"url": {
|
|
483
|
+
"type": "string",
|
|
484
|
+
"minLength": 1,
|
|
485
|
+
"description": "The webhook URL to deliver events to"
|
|
486
|
+
},
|
|
487
|
+
"enabled": {
|
|
488
|
+
"type": "boolean",
|
|
489
|
+
"default": true,
|
|
490
|
+
"description": "Whether the endpoint is active"
|
|
491
|
+
},
|
|
492
|
+
"domain_id": {
|
|
493
|
+
"type": [
|
|
494
|
+
"string",
|
|
495
|
+
"null"
|
|
496
|
+
],
|
|
497
|
+
"format": "uuid",
|
|
498
|
+
"description": "Restrict to emails from a specific domain"
|
|
499
|
+
},
|
|
500
|
+
"rules": {
|
|
501
|
+
"type": "object",
|
|
502
|
+
"description": "Endpoint-specific filtering rules"
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
"required": [
|
|
506
|
+
"url"
|
|
507
|
+
]
|
|
508
|
+
},
|
|
410
509
|
"sdkName": "createEndpoint",
|
|
411
510
|
"summary": "Create a webhook endpoint",
|
|
412
511
|
"tag": "Endpoints",
|
|
@@ -431,6 +530,7 @@ export const operationManifest = [
|
|
|
431
530
|
}
|
|
432
531
|
],
|
|
433
532
|
"queryParams": [],
|
|
533
|
+
"requestSchema": null,
|
|
434
534
|
"sdkName": "deleteEndpoint",
|
|
435
535
|
"summary": "Delete a webhook endpoint",
|
|
436
536
|
"tag": "Endpoints",
|
|
@@ -447,6 +547,7 @@ export const operationManifest = [
|
|
|
447
547
|
"path": "/endpoints",
|
|
448
548
|
"pathParams": [],
|
|
449
549
|
"queryParams": [],
|
|
550
|
+
"requestSchema": null,
|
|
450
551
|
"sdkName": "listEndpoints",
|
|
451
552
|
"summary": "List webhook endpoints",
|
|
452
553
|
"tag": "Endpoints",
|
|
@@ -471,6 +572,7 @@ export const operationManifest = [
|
|
|
471
572
|
}
|
|
472
573
|
],
|
|
473
574
|
"queryParams": [],
|
|
575
|
+
"requestSchema": null,
|
|
474
576
|
"sdkName": "testEndpoint",
|
|
475
577
|
"summary": "Send a test webhook",
|
|
476
578
|
"tag": "Endpoints",
|
|
@@ -495,6 +597,31 @@ export const operationManifest = [
|
|
|
495
597
|
}
|
|
496
598
|
],
|
|
497
599
|
"queryParams": [],
|
|
600
|
+
"requestSchema": {
|
|
601
|
+
"type": "object",
|
|
602
|
+
"additionalProperties": false,
|
|
603
|
+
"properties": {
|
|
604
|
+
"url": {
|
|
605
|
+
"type": "string",
|
|
606
|
+
"minLength": 1,
|
|
607
|
+
"description": "New webhook URL (triggers endpoint rotation)"
|
|
608
|
+
},
|
|
609
|
+
"enabled": {
|
|
610
|
+
"type": "boolean"
|
|
611
|
+
},
|
|
612
|
+
"domain_id": {
|
|
613
|
+
"type": [
|
|
614
|
+
"string",
|
|
615
|
+
"null"
|
|
616
|
+
],
|
|
617
|
+
"format": "uuid"
|
|
618
|
+
},
|
|
619
|
+
"rules": {
|
|
620
|
+
"type": "object"
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
"minProperties": 1
|
|
624
|
+
},
|
|
498
625
|
"sdkName": "updateEndpoint",
|
|
499
626
|
"summary": "Update a webhook endpoint",
|
|
500
627
|
"tag": "Endpoints",
|
|
@@ -511,6 +638,37 @@ export const operationManifest = [
|
|
|
511
638
|
"path": "/filters",
|
|
512
639
|
"pathParams": [],
|
|
513
640
|
"queryParams": [],
|
|
641
|
+
"requestSchema": {
|
|
642
|
+
"type": "object",
|
|
643
|
+
"additionalProperties": false,
|
|
644
|
+
"properties": {
|
|
645
|
+
"type": {
|
|
646
|
+
"type": "string",
|
|
647
|
+
"enum": [
|
|
648
|
+
"whitelist",
|
|
649
|
+
"blocklist"
|
|
650
|
+
]
|
|
651
|
+
},
|
|
652
|
+
"pattern": {
|
|
653
|
+
"type": "string",
|
|
654
|
+
"minLength": 1,
|
|
655
|
+
"maxLength": 500,
|
|
656
|
+
"description": "Email address or pattern to filter"
|
|
657
|
+
},
|
|
658
|
+
"domain_id": {
|
|
659
|
+
"type": [
|
|
660
|
+
"string",
|
|
661
|
+
"null"
|
|
662
|
+
],
|
|
663
|
+
"format": "uuid",
|
|
664
|
+
"description": "Restrict filter to a specific domain (Pro plan required)"
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
"required": [
|
|
668
|
+
"type",
|
|
669
|
+
"pattern"
|
|
670
|
+
]
|
|
671
|
+
},
|
|
514
672
|
"sdkName": "createFilter",
|
|
515
673
|
"summary": "Create a filter rule",
|
|
516
674
|
"tag": "Filters",
|
|
@@ -535,6 +693,7 @@ export const operationManifest = [
|
|
|
535
693
|
}
|
|
536
694
|
],
|
|
537
695
|
"queryParams": [],
|
|
696
|
+
"requestSchema": null,
|
|
538
697
|
"sdkName": "deleteFilter",
|
|
539
698
|
"summary": "Delete a filter rule",
|
|
540
699
|
"tag": "Filters",
|
|
@@ -551,6 +710,7 @@ export const operationManifest = [
|
|
|
551
710
|
"path": "/filters",
|
|
552
711
|
"pathParams": [],
|
|
553
712
|
"queryParams": [],
|
|
713
|
+
"requestSchema": null,
|
|
554
714
|
"sdkName": "listFilters",
|
|
555
715
|
"summary": "List filter rules",
|
|
556
716
|
"tag": "Filters",
|
|
@@ -575,11 +735,104 @@ export const operationManifest = [
|
|
|
575
735
|
}
|
|
576
736
|
],
|
|
577
737
|
"queryParams": [],
|
|
738
|
+
"requestSchema": {
|
|
739
|
+
"type": "object",
|
|
740
|
+
"additionalProperties": false,
|
|
741
|
+
"properties": {
|
|
742
|
+
"enabled": {
|
|
743
|
+
"type": "boolean"
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
"required": [
|
|
747
|
+
"enabled"
|
|
748
|
+
]
|
|
749
|
+
},
|
|
578
750
|
"sdkName": "updateFilter",
|
|
579
751
|
"summary": "Update a filter rule",
|
|
580
752
|
"tag": "Filters",
|
|
581
753
|
"tagCommand": "filters"
|
|
582
754
|
},
|
|
755
|
+
{
|
|
756
|
+
"binaryResponse": false,
|
|
757
|
+
"bodyRequired": true,
|
|
758
|
+
"command": "send-email",
|
|
759
|
+
"description": "Sends an outbound email through Primitive's outbound relay. By default\nthe request returns once the relay accepts the message for delivery.\nSet `wait: true` to wait for the first downstream SMTP delivery outcome.\n",
|
|
760
|
+
"hasJsonBody": true,
|
|
761
|
+
"method": "POST",
|
|
762
|
+
"operationId": "sendEmail",
|
|
763
|
+
"path": "/send-mail",
|
|
764
|
+
"pathParams": [],
|
|
765
|
+
"queryParams": [],
|
|
766
|
+
"requestSchema": {
|
|
767
|
+
"type": "object",
|
|
768
|
+
"additionalProperties": false,
|
|
769
|
+
"properties": {
|
|
770
|
+
"from": {
|
|
771
|
+
"type": "string",
|
|
772
|
+
"minLength": 3,
|
|
773
|
+
"maxLength": 998,
|
|
774
|
+
"description": "RFC 5322 From header. The sender domain must be a verified outbound domain for your organization."
|
|
775
|
+
},
|
|
776
|
+
"to": {
|
|
777
|
+
"type": "string",
|
|
778
|
+
"minLength": 3,
|
|
779
|
+
"maxLength": 320,
|
|
780
|
+
"description": "Recipient address. Recipient eligibility depends on your account's outbound entitlements."
|
|
781
|
+
},
|
|
782
|
+
"subject": {
|
|
783
|
+
"type": "string",
|
|
784
|
+
"minLength": 1,
|
|
785
|
+
"maxLength": 998,
|
|
786
|
+
"description": "Subject line for the outbound message"
|
|
787
|
+
},
|
|
788
|
+
"body_text": {
|
|
789
|
+
"type": "string",
|
|
790
|
+
"description": "Plain-text message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes."
|
|
791
|
+
},
|
|
792
|
+
"body_html": {
|
|
793
|
+
"type": "string",
|
|
794
|
+
"description": "HTML message body. At least one of body_text or body_html is required. The combined UTF-8 byte length of body_text and body_html must be at most 262144 bytes."
|
|
795
|
+
},
|
|
796
|
+
"in_reply_to": {
|
|
797
|
+
"type": "string",
|
|
798
|
+
"minLength": 1,
|
|
799
|
+
"maxLength": 998,
|
|
800
|
+
"pattern": "^[^\\x00-\\x1F\\x7F]+$",
|
|
801
|
+
"description": "Message-ID of the direct parent email when sending a threaded reply."
|
|
802
|
+
},
|
|
803
|
+
"references": {
|
|
804
|
+
"type": "array",
|
|
805
|
+
"maxItems": 100,
|
|
806
|
+
"description": "Full ordered message-id chain for the thread.",
|
|
807
|
+
"items": {
|
|
808
|
+
"type": "string",
|
|
809
|
+
"minLength": 1,
|
|
810
|
+
"maxLength": 998,
|
|
811
|
+
"pattern": "^[^\\x00-\\x1F\\x7F]+$"
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
"wait": {
|
|
815
|
+
"type": "boolean",
|
|
816
|
+
"description": "When true, wait for the first downstream SMTP delivery outcome before returning."
|
|
817
|
+
},
|
|
818
|
+
"wait_timeout_ms": {
|
|
819
|
+
"type": "integer",
|
|
820
|
+
"minimum": 1000,
|
|
821
|
+
"maximum": 30000,
|
|
822
|
+
"description": "Maximum time to wait for a delivery outcome when wait is true. Defaults to 30000."
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
"required": [
|
|
826
|
+
"from",
|
|
827
|
+
"to",
|
|
828
|
+
"subject"
|
|
829
|
+
]
|
|
830
|
+
},
|
|
831
|
+
"sdkName": "sendEmail",
|
|
832
|
+
"summary": "Send outbound email",
|
|
833
|
+
"tag": "Sending",
|
|
834
|
+
"tagCommand": "sending"
|
|
835
|
+
},
|
|
583
836
|
{
|
|
584
837
|
"binaryResponse": false,
|
|
585
838
|
"bodyRequired": false,
|
|
@@ -639,6 +892,7 @@ export const operationManifest = [
|
|
|
639
892
|
"type": "string"
|
|
640
893
|
}
|
|
641
894
|
],
|
|
895
|
+
"requestSchema": null,
|
|
642
896
|
"sdkName": "listDeliveries",
|
|
643
897
|
"summary": "List webhook deliveries",
|
|
644
898
|
"tag": "Webhook Deliveries",
|
|
@@ -663,6 +917,7 @@ export const operationManifest = [
|
|
|
663
917
|
}
|
|
664
918
|
],
|
|
665
919
|
"queryParams": [],
|
|
920
|
+
"requestSchema": null,
|
|
666
921
|
"sdkName": "replayDelivery",
|
|
667
922
|
"summary": "Replay a webhook delivery",
|
|
668
923
|
"tag": "Webhook Deliveries",
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import addressparser from "nodemailer/lib/addressparser/index.js";
|
|
2
|
+
import isEmail from "validator/lib/isEmail.js";
|
|
3
|
+
// Per RFC 5322 §2.1.1, header lines are bounded at 998 octets. We measure
|
|
4
|
+
// in UTF-8 bytes, not JS code units, so SMTPUTF8 (RFC 6531) headers with
|
|
5
|
+
// multi-byte characters cannot bypass the cap by being short on chars but
|
|
6
|
+
// long on bytes. Reject anything beyond as malformed without parsing: a
|
|
7
|
+
// longer From field is either a header-injection probe or a corrupt feed.
|
|
8
|
+
const MAX_HEADER_LENGTH = 998;
|
|
9
|
+
// Options for validator's isEmail. The per-part length limits (64-octet
|
|
10
|
+
// local-part, 255-octet domain), dot-atom rules, hostname-label rules,
|
|
11
|
+
// and TLD requirement are all enforced inside isEmail. We choose:
|
|
12
|
+
// allow_ip_domain: true -- accept user@[192.168.1.1] address-literals
|
|
13
|
+
// require_tld: true -- reject user@localhost
|
|
14
|
+
// allow_display_name: false -- we already extracted the address with
|
|
15
|
+
// addressparser, so isEmail only sees the
|
|
16
|
+
// bare addr-spec
|
|
17
|
+
// allow_utf8_local_part: true -- accept SMTPUTF8 / EAI local-parts
|
|
18
|
+
const IS_EMAIL_OPTIONS = {
|
|
19
|
+
allow_ip_domain: true,
|
|
20
|
+
require_tld: true,
|
|
21
|
+
allow_display_name: false,
|
|
22
|
+
allow_utf8_local_part: true,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Strict parser for RFC 5322 From-style headers in security-bearing
|
|
26
|
+
* contexts (allowlist gates, permission grants).
|
|
27
|
+
*
|
|
28
|
+
* Rejects, without falling back to a "best guess":
|
|
29
|
+
* - empty / whitespace-only input
|
|
30
|
+
* - inputs longer than RFC 5322's 998-octet line limit
|
|
31
|
+
* - multi-address From (RFC 5322 allows it but it is vanishingly
|
|
32
|
+
* rare and ambiguous as an identity)
|
|
33
|
+
* - group syntax ("Friends: a@b.com, c@d.com;")
|
|
34
|
+
* - any address that fails validator's isEmail check with our chosen
|
|
35
|
+
* options. That covers per-part length limits, dot-atom rules,
|
|
36
|
+
* hostname-label rules, TLD requirement, and other RFC 5321/5322
|
|
37
|
+
* conformance checks.
|
|
38
|
+
*
|
|
39
|
+
* Returns ONLY the validated address, with no display name. Strict
|
|
40
|
+
* exists for gating decisions, where the address is the security-
|
|
41
|
+
* bearing field. Display names from addressparser are not trustworthy
|
|
42
|
+
* here: weird inputs like `Name <user@x.com> <attacker@y.com>` get
|
|
43
|
+
* parsed as a single entry whose `name` silently includes the second
|
|
44
|
+
* address. Surfacing that as a "parsed name" would invite downstream
|
|
45
|
+
* misuse, so we drop it. If you need the name, call
|
|
46
|
+
* {@link parseFromHeaderLoose} alongside (it returns null on failure
|
|
47
|
+
* anyway, so you can still gate on strict's Result).
|
|
48
|
+
*
|
|
49
|
+
* Returns a typed Result so callers can map the failure reason to
|
|
50
|
+
* stable error codes without inspecting message text.
|
|
51
|
+
*/
|
|
52
|
+
export function parseFromHeader(header) {
|
|
53
|
+
if (header === null || header === undefined) {
|
|
54
|
+
return { ok: false, reason: "empty" };
|
|
55
|
+
}
|
|
56
|
+
const trimmed = header.trim();
|
|
57
|
+
if (trimmed.length === 0) {
|
|
58
|
+
return { ok: false, reason: "empty" };
|
|
59
|
+
}
|
|
60
|
+
if (Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) {
|
|
61
|
+
return { ok: false, reason: "too_long" };
|
|
62
|
+
}
|
|
63
|
+
// Default (no flatten) so group entries surface as { name, group: [] }
|
|
64
|
+
// rather than being silently merged into the address list.
|
|
65
|
+
const parsed = addressparser(trimmed);
|
|
66
|
+
if (parsed.length > 1) {
|
|
67
|
+
return { ok: false, reason: "multiple_addresses" };
|
|
68
|
+
}
|
|
69
|
+
const entry = parsed[0];
|
|
70
|
+
// addressparser returns a single entry with empty `address` for raw
|
|
71
|
+
// garbage rather than an empty array, so an empty result is only
|
|
72
|
+
// possible for inputs that already failed our trim/empty check above.
|
|
73
|
+
// The defensive fall-through maps any future regression to
|
|
74
|
+
// invalid_address rather than crashing on parsed[0].
|
|
75
|
+
if (entry === undefined) {
|
|
76
|
+
return { ok: false, reason: "invalid_address" };
|
|
77
|
+
}
|
|
78
|
+
if ("group" in entry) {
|
|
79
|
+
return { ok: false, reason: "group_syntax" };
|
|
80
|
+
}
|
|
81
|
+
const address = entry.address;
|
|
82
|
+
if (address === undefined || !isEmail(address, IS_EMAIL_OPTIONS)) {
|
|
83
|
+
return { ok: false, reason: "invalid_address" };
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
value: { address: address.toLowerCase() },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Lenient parser for display-only call sites (inbox card "from",
|
|
92
|
+
* log lines, debugging). Returns the first parseable address with its
|
|
93
|
+
* display name, or null.
|
|
94
|
+
*
|
|
95
|
+
* Differences from {@link parseFromHeader}:
|
|
96
|
+
* - Multi-address From returns the first address instead of rejecting
|
|
97
|
+
* - Group syntax is flattened into its member addresses
|
|
98
|
+
* - Returns null instead of a typed reason on failure
|
|
99
|
+
* - Includes the parsed display name in the result
|
|
100
|
+
*
|
|
101
|
+
* Do not use for permission gates or any decision that grants access.
|
|
102
|
+
* That is what {@link parseFromHeader} is for. Names returned here can
|
|
103
|
+
* include addressparser's recovery output (trailing tokens, garbage
|
|
104
|
+
* before the address); treat as opaque text for display.
|
|
105
|
+
*/
|
|
106
|
+
export function parseFromHeaderLoose(header) {
|
|
107
|
+
if (header === null || header === undefined) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const trimmed = header.trim();
|
|
111
|
+
if (trimmed.length === 0 ||
|
|
112
|
+
Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const parsed = addressparser(trimmed);
|
|
116
|
+
for (const entry of parsed) {
|
|
117
|
+
const candidates = "group" in entry && Array.isArray(entry.group) ? entry.group : [entry];
|
|
118
|
+
for (const candidate of candidates) {
|
|
119
|
+
const address = candidate.address;
|
|
120
|
+
if (address !== undefined && isEmail(address, IS_EMAIL_OPTIONS)) {
|
|
121
|
+
return {
|
|
122
|
+
address: address.toLowerCase(),
|
|
123
|
+
name: candidate.name && candidate.name.length > 0 ? candidate.name : null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
package/dist/parser/index.d.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { M as WebhookAttachment, S as ParsedDataComplete, s as EmailAddress } from "../types-9vXGZjPd.js";
|
|
2
2
|
|
|
3
3
|
//#region src/parser/address-parser.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* A validated RFC 5322 address. Returned by the strict parser, which
|
|
6
|
-
* deliberately does not expose a display name.
|
|
7
|
-
*
|
|
8
|
-
* `address` is normalized to lowercase. Both the local-part and the
|
|
9
|
-
* domain are lowercased: RFC 5321 §2.4 permits case-sensitive local-
|
|
10
|
-
* parts, but every consumer mailbox in practice treats them as
|
|
11
|
-
* case-insensitive, and a case-sensitive grant key would split
|
|
12
|
-
* `Bob@x.com` from `bob@x.com` into separate rows and defeat the
|
|
13
|
-
* primary-key index on lookup.
|
|
14
|
-
*/
|
|
15
4
|
/**
|
|
16
5
|
* A validated RFC 5322 address. Returned by the strict parser, which
|
|
17
6
|
* deliberately does not expose a display name.
|
|
@@ -99,7 +88,8 @@ declare function parseFromHeader(header: string | null | undefined): ParseFromHe
|
|
|
99
88
|
* include addressparser's recovery output (trailing tokens, garbage
|
|
100
89
|
* before the address); treat as opaque text for display.
|
|
101
90
|
*/
|
|
102
|
-
declare function parseFromHeaderLoose(header: string | null | undefined): ParsedAddress | null;
|
|
91
|
+
declare function parseFromHeaderLoose(header: string | null | undefined): ParsedAddress | null;
|
|
92
|
+
//#endregion
|
|
103
93
|
//#region src/parser/attachment-parser.d.ts
|
|
104
94
|
interface ParsedAttachment {
|
|
105
95
|
id: string;
|
|
@@ -162,7 +152,6 @@ declare function sha256Hex(buffer: Buffer): string;
|
|
|
162
152
|
* Prevents path traversal, removes control characters, enforces length limits.
|
|
163
153
|
*/
|
|
164
154
|
declare function sanitizeFilename(filename: string | null, partIndex: number): string;
|
|
165
|
-
|
|
166
155
|
//#endregion
|
|
167
156
|
//#region src/parser/attachment-bundler.d.ts
|
|
168
157
|
/**
|
|
@@ -216,7 +205,6 @@ declare function extractAttachmentMetadata(attachments: ParsedAttachment[]): Att
|
|
|
216
205
|
* @returns Storage key in format: attachments/{email_id}_{hash8}.tar.gz
|
|
217
206
|
*/
|
|
218
207
|
declare function getAttachmentsStorageKey(emailId: string, sha256: string): string;
|
|
219
|
-
|
|
220
208
|
//#endregion
|
|
221
209
|
//#region src/parser/email-parser.d.ts
|
|
222
210
|
interface ParsedEmail {
|
|
@@ -234,7 +222,6 @@ interface ParsedEmail {
|
|
|
234
222
|
* Uses mailparser library for robust email parsing
|
|
235
223
|
*/
|
|
236
224
|
declare function parseEmail(emlRaw: string): Promise<ParsedEmail>;
|
|
237
|
-
|
|
238
225
|
//#endregion
|
|
239
226
|
//#region src/parser/mapping.d.ts
|
|
240
227
|
/**
|
|
@@ -283,10 +270,8 @@ declare function toCanonicalHeaders(parsed: ParsedEmailWithAttachments): {
|
|
|
283
270
|
to: string;
|
|
284
271
|
date: string | null;
|
|
285
272
|
};
|
|
286
|
-
|
|
287
273
|
//#endregion
|
|
288
274
|
//#region src/parser/sanitize-html.d.ts
|
|
289
275
|
declare function sanitizeHtml(html: string): string;
|
|
290
|
-
|
|
291
276
|
//#endregion
|
|
292
|
-
export { AttachmentMetadata, BundleResult, ParseFromHeaderFailureReason, ParseFromHeaderResult, ParsedAddress, ParsedAttachment, ParsedEmail, ParsedEmailWithAttachments, ValidatedAddress, attachmentMetadataToWebhookAttachments, bundleAttachments, extractAttachmentMetadata, getAttachmentsStorageKey, normalizeContentType, parseEmail, parseEmailWithAttachments, parseFromHeader, parseFromHeaderLoose, sanitizeFilename, sanitizeHtml, sha256Hex, toCanonicalHeaders, toParsedDataComplete, toWebhookAttachments };
|
|
277
|
+
export { type AttachmentMetadata, type BundleResult, type ParseFromHeaderFailureReason, type ParseFromHeaderResult, type ParsedAddress, type ParsedAttachment, type ParsedEmail, type ParsedEmailWithAttachments, type ValidatedAddress, attachmentMetadataToWebhookAttachments, bundleAttachments, extractAttachmentMetadata, getAttachmentsStorageKey, normalizeContentType, parseEmail, parseEmailWithAttachments, parseFromHeader, parseFromHeaderLoose, sanitizeFilename, sanitizeHtml, sha256Hex, toCanonicalHeaders, toParsedDataComplete, toWebhookAttachments };
|