@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.
@@ -1,114 +1,9 @@
1
+ import { n as parseFromHeaderLoose, t as parseFromHeader } from "../address-parser-BYn8oW5r.js";
1
2
  import { createHash } from "node:crypto";
2
- import addressparser from "nodemailer/lib/addressparser/index.js";
3
- import isEmail from "validator/lib/isEmail.js";
4
3
  import { createGzip } from "node:zlib";
5
4
  import { pack } from "tar-stream";
6
5
  import { simpleParser } from "mailparser";
7
6
  import DOMPurify from "isomorphic-dompurify";
8
-
9
- //#region src/parser/address-parser.ts
10
- const MAX_HEADER_LENGTH = 998;
11
- const IS_EMAIL_OPTIONS = {
12
- allow_ip_domain: true,
13
- require_tld: true,
14
- allow_display_name: false,
15
- allow_utf8_local_part: true
16
- };
17
- /**
18
- * Strict parser for RFC 5322 From-style headers in security-bearing
19
- * contexts (allowlist gates, permission grants).
20
- *
21
- * Rejects, without falling back to a "best guess":
22
- * - empty / whitespace-only input
23
- * - inputs longer than RFC 5322's 998-octet line limit
24
- * - multi-address From (RFC 5322 allows it but it is vanishingly
25
- * rare and ambiguous as an identity)
26
- * - group syntax ("Friends: a@b.com, c@d.com;")
27
- * - any address that fails validator's isEmail check with our chosen
28
- * options. That covers per-part length limits, dot-atom rules,
29
- * hostname-label rules, TLD requirement, and other RFC 5321/5322
30
- * conformance checks.
31
- *
32
- * Returns ONLY the validated address, with no display name. Strict
33
- * exists for gating decisions, where the address is the security-
34
- * bearing field. Display names from addressparser are not trustworthy
35
- * here: weird inputs like `Name <user@x.com> <attacker@y.com>` get
36
- * parsed as a single entry whose `name` silently includes the second
37
- * address. Surfacing that as a "parsed name" would invite downstream
38
- * misuse, so we drop it. If you need the name, call
39
- * {@link parseFromHeaderLoose} alongside (it returns null on failure
40
- * anyway, so you can still gate on strict's Result).
41
- *
42
- * Returns a typed Result so callers can map the failure reason to
43
- * stable error codes without inspecting message text.
44
- */
45
- function parseFromHeader(header) {
46
- if (header === null || header === void 0) return {
47
- ok: false,
48
- reason: "empty"
49
- };
50
- const trimmed = header.trim();
51
- if (trimmed.length === 0) return {
52
- ok: false,
53
- reason: "empty"
54
- };
55
- if (Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) return {
56
- ok: false,
57
- reason: "too_long"
58
- };
59
- const parsed = addressparser(trimmed);
60
- if (parsed.length > 1) return {
61
- ok: false,
62
- reason: "multiple_addresses"
63
- };
64
- const entry = parsed[0];
65
- if (entry === void 0) return {
66
- ok: false,
67
- reason: "invalid_address"
68
- };
69
- if ("group" in entry) return {
70
- ok: false,
71
- reason: "group_syntax"
72
- };
73
- if (!isEmail(entry.address, IS_EMAIL_OPTIONS)) return {
74
- ok: false,
75
- reason: "invalid_address"
76
- };
77
- return {
78
- ok: true,
79
- value: { address: entry.address.toLowerCase() }
80
- };
81
- }
82
- /**
83
- * Lenient parser for display-only call sites (inbox card "from",
84
- * log lines, debugging). Returns the first parseable address with its
85
- * display name, or null.
86
- *
87
- * Differences from {@link parseFromHeader}:
88
- * - Multi-address From returns the first address instead of rejecting
89
- * - Group syntax is flattened into its member addresses
90
- * - Returns null instead of a typed reason on failure
91
- * - Includes the parsed display name in the result
92
- *
93
- * Do not use for permission gates or any decision that grants access.
94
- * That is what {@link parseFromHeader} is for. Names returned here can
95
- * include addressparser's recovery output (trailing tokens, garbage
96
- * before the address); treat as opaque text for display.
97
- */
98
- function parseFromHeaderLoose(header) {
99
- if (header === null || header === void 0) return null;
100
- const trimmed = header.trim();
101
- if (trimmed.length === 0 || Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) return null;
102
- const parsed = addressparser(trimmed, { flatten: true });
103
- const entry = parsed[0];
104
- if (entry === void 0 || !isEmail(entry.address, IS_EMAIL_OPTIONS)) return null;
105
- return {
106
- address: entry.address.toLowerCase(),
107
- name: entry.name && entry.name.length > 0 ? entry.name : null
108
- };
109
- }
110
-
111
- //#endregion
112
7
  //#region src/parser/attachment-bundler.ts
113
8
  function appendTarEntry(archive, name, content) {
114
9
  return new Promise((resolve, reject) => {
@@ -185,10 +80,9 @@ async function bundleAttachments(attachments) {
185
80
  }
186
81
  })();
187
82
  });
188
- const sha256 = createHash("sha256").update(tarGzBuffer).digest("hex");
189
83
  return {
190
84
  tarGzBuffer,
191
- sha256,
85
+ sha256: createHash("sha256").update(tarGzBuffer).digest("hex"),
192
86
  attachmentCount: downloadable.length,
193
87
  totalAttachmentBytes
194
88
  };
@@ -218,10 +112,8 @@ function extractAttachmentMetadata(attachments) {
218
112
  * @returns Storage key in format: attachments/{email_id}_{hash8}.tar.gz
219
113
  */
220
114
  function getAttachmentsStorageKey(emailId, sha256) {
221
- const hash8 = sha256.substring(0, 8);
222
- return `attachments/${emailId}_${hash8}.tar.gz`;
115
+ return `attachments/${emailId}_${sha256.substring(0, 8)}.tar.gz`;
223
116
  }
224
-
225
117
  //#endregion
226
118
  //#region src/parser/sanitize-html.ts
227
119
  const ALLOWED_TAGS = [
@@ -307,8 +199,7 @@ DOMPurify.addHook("uponSanitizeAttribute", (_node, data) => {
307
199
  });
308
200
  DOMPurify.addHook("afterSanitizeAttributes", (node) => {
309
201
  if (node.tagName === "A") {
310
- const target = node.getAttribute("target");
311
- if (target === "_blank") node.setAttribute("rel", "noopener noreferrer");
202
+ if (node.getAttribute("target") === "_blank") node.setAttribute("rel", "noopener noreferrer");
312
203
  }
313
204
  });
314
205
  const SANITIZE_OPTIONS = {
@@ -338,7 +229,6 @@ const SANITIZE_OPTIONS = {
338
229
  function sanitizeHtml(html) {
339
230
  return DOMPurify.sanitize(html, SANITIZE_OPTIONS);
340
231
  }
341
-
342
232
  //#endregion
343
233
  //#region src/parser/attachment-parser.ts
344
234
  const SIGNATURE_ARTIFACTS = new Set([
@@ -449,8 +339,7 @@ function normalizeReferences(refs) {
449
339
  */
450
340
  function normalizeContentType(contentType) {
451
341
  if (!contentType?.trim()) return "application/octet-stream";
452
- const mediaType = contentType.split(";")[0].trim().toLowerCase();
453
- return mediaType || "application/octet-stream";
342
+ return contentType.split(";")[0].trim().toLowerCase() || "application/octet-stream";
454
343
  }
455
344
  /**
456
345
  * Parse disposition string to typed value.
@@ -479,8 +368,7 @@ function getHeaderString(value) {
479
368
  return String(value);
480
369
  }
481
370
  function getOriginalHeaderValue(parsed, key) {
482
- const headerLines = parsed.headerLines;
483
- const original = headerLines?.find((header) => header.key?.toLowerCase() === key.toLowerCase())?.line;
371
+ const original = parsed.headerLines?.find((header) => header.key?.toLowerCase() === key.toLowerCase())?.line;
484
372
  if (!original) return null;
485
373
  const separator = original.indexOf(":");
486
374
  return separator === -1 ? original : original.slice(separator + 1).trimStart();
@@ -510,7 +398,6 @@ function sanitizeFilename(filename, partIndex) {
510
398
  if (!safe) return `attachment_${partIndex}`;
511
399
  return safe;
512
400
  }
513
-
514
401
  //#endregion
515
402
  //#region src/parser/email-parser.ts
516
403
  /**
@@ -570,7 +457,6 @@ function structuredHeaderToString(value) {
570
457
  }
571
458
  return JSON.stringify(value);
572
459
  }
573
-
574
460
  //#endregion
575
461
  //#region src/parser/mapping.ts
576
462
  /**
@@ -660,6 +546,5 @@ function requireNonEmptyHeader(value, headerName) {
660
546
  if (typeof value !== "string" || value.trim() === "") throw new Error(`Parsed email is missing a usable ${headerName} header value`);
661
547
  return value;
662
548
  }
663
-
664
549
  //#endregion
665
- export { attachmentMetadataToWebhookAttachments, bundleAttachments, extractAttachmentMetadata, getAttachmentsStorageKey, normalizeContentType, parseEmail, parseEmailWithAttachments, parseFromHeader, parseFromHeaderLoose, sanitizeFilename, sanitizeHtml, sha256Hex, toCanonicalHeaders, toParsedDataComplete, toWebhookAttachments };
550
+ export { attachmentMetadataToWebhookAttachments, bundleAttachments, extractAttachmentMetadata, getAttachmentsStorageKey, normalizeContentType, parseEmail, parseEmailWithAttachments, parseFromHeader, parseFromHeaderLoose, sanitizeFilename, sanitizeHtml, sha256Hex, toCanonicalHeaders, toParsedDataComplete, toWebhookAttachments };
@@ -0,0 +1,69 @@
1
+ import { n as parseFromHeaderLoose } from "./address-parser-BYn8oW5r.js";
2
+ //#region src/webhook/received-email.ts
3
+ const REPLY_PREFIX_RE = /^re\s*:/i;
4
+ const FORWARD_PREFIX_RE = /^(fwd?|fw)\s*:/i;
5
+ function normalizeReceivedEmail(event) {
6
+ const receivedBy = event.email.smtp.rcpt_to[0];
7
+ if (!receivedBy) throw new Error("email.smtp.rcpt_to must contain at least one recipient");
8
+ const sender = parseHeaderAddress(event.email.headers.from) ?? {
9
+ address: event.email.smtp.mail_from.trim().toLowerCase(),
10
+ name: null
11
+ };
12
+ const replyTarget = firstStructuredAddress(event.email.parsed.reply_to) ?? sender;
13
+ const subject = event.email.headers.subject ?? null;
14
+ const references = event.email.parsed.references ?? [];
15
+ const messageId = event.email.headers.message_id ?? null;
16
+ return {
17
+ id: event.email.id,
18
+ eventId: event.id,
19
+ receivedAt: event.email.received_at,
20
+ sender,
21
+ replyTarget,
22
+ receivedBy,
23
+ receivedByAll: [...event.email.smtp.rcpt_to],
24
+ subject,
25
+ replySubject: buildReplySubject(subject),
26
+ forwardSubject: buildForwardSubject(subject),
27
+ text: event.email.parsed.body_text ?? null,
28
+ thread: {
29
+ messageId,
30
+ inReplyTo: event.email.parsed.in_reply_to ?? [],
31
+ references
32
+ },
33
+ attachments: event.email.parsed.attachments ?? [],
34
+ auth: event.email.auth,
35
+ analysis: event.email.analysis,
36
+ raw: event
37
+ };
38
+ }
39
+ function buildReplySubject(subject) {
40
+ const trimmed = subject?.trim() ?? "";
41
+ if (trimmed.length === 0) return "Re:";
42
+ return REPLY_PREFIX_RE.test(trimmed) ? trimmed : `Re: ${trimmed}`;
43
+ }
44
+ function buildForwardSubject(subject) {
45
+ const trimmed = subject?.trim() ?? "";
46
+ if (trimmed.length === 0) return "Fwd:";
47
+ return FORWARD_PREFIX_RE.test(trimmed) ? trimmed : `Fwd: ${trimmed}`;
48
+ }
49
+ function formatAddress(address) {
50
+ return address.name ? `${address.name} <${address.address}>` : address.address;
51
+ }
52
+ function firstStructuredAddress(addresses) {
53
+ const address = addresses?.[0];
54
+ if (!address) return null;
55
+ return {
56
+ address: address.address.trim().toLowerCase(),
57
+ name: address.name ?? null
58
+ };
59
+ }
60
+ function parseHeaderAddress(value) {
61
+ const parsed = parseFromHeaderLoose(value);
62
+ if (!parsed) return null;
63
+ return {
64
+ address: parsed.address,
65
+ name: parsed.name?.trim() || null
66
+ };
67
+ }
68
+ //#endregion
69
+ export { parseHeaderAddress as a, normalizeReceivedEmail as i, buildReplySubject as n, formatAddress as r, buildForwardSubject as t };
@@ -0,0 +1,37 @@
1
+ import { M as WebhookAttachment, c as EmailAnalysis, l as EmailAuth, u as EmailReceivedEvent } from "./types-9vXGZjPd.js";
2
+
3
+ //#region src/webhook/received-email.d.ts
4
+ interface ReceivedEmailAddress {
5
+ address: string;
6
+ name: string | null;
7
+ }
8
+ interface ReceivedEmailThread {
9
+ messageId: string | null;
10
+ inReplyTo: string[];
11
+ references: string[];
12
+ }
13
+ interface ReceivedEmail {
14
+ id: string;
15
+ eventId: string;
16
+ receivedAt: string;
17
+ sender: ReceivedEmailAddress;
18
+ replyTarget: ReceivedEmailAddress;
19
+ receivedBy: string;
20
+ receivedByAll: string[];
21
+ subject: string | null;
22
+ replySubject: string;
23
+ forwardSubject: string;
24
+ text: string | null;
25
+ thread: ReceivedEmailThread;
26
+ attachments: WebhookAttachment[];
27
+ auth: EmailAuth;
28
+ analysis: EmailAnalysis;
29
+ raw: EmailReceivedEvent;
30
+ }
31
+ declare function normalizeReceivedEmail(event: EmailReceivedEvent): ReceivedEmail;
32
+ declare function buildReplySubject(subject: string | null | undefined): string;
33
+ declare function buildForwardSubject(subject: string | null | undefined): string;
34
+ declare function formatAddress(address: ReceivedEmailAddress): string;
35
+ declare function parseHeaderAddress(value: string | null | undefined): ReceivedEmailAddress | null;
36
+ //#endregion
37
+ export { buildReplySubject as a, parseHeaderAddress as c, buildForwardSubject as i, ReceivedEmailAddress as n, formatAddress as o, ReceivedEmailThread as r, normalizeReceivedEmail as s, ReceivedEmail as t };
@@ -17,13 +17,6 @@
17
17
  * via the `definition` "ForwardResult".
18
18
  */
19
19
  type ForwardResult$1 = (ForwardResultInline$1 | ForwardResultAttachmentAnalyzed$1 | ForwardResultAttachmentSkipped$1);
20
- /**
21
- * Valid webhook version format (YYYY-MM-DD date string). The SDK accepts any valid date-formatted version, not just the current one, for forward and backward compatibility.
22
- *
23
- * This interface was referenced by `EmailReceivedEvent`'s JSON-Schema
24
- * via the `definition` "WebhookVersion".
25
- */
26
-
27
20
  /**
28
21
  * Webhook payload for the `email.received` event.
29
22
  *
@@ -693,17 +686,9 @@ interface DkimSignature$1 {
693
686
  * Optional in self-hosted environments.
694
687
  */
695
688
  algo: (string | null);
696
- } //#endregion
689
+ }
690
+ //#endregion
697
691
  //#region src/types.d.ts
698
-
699
- /**
700
- * Webhook payload for the `email.received` event.
701
- *
702
- * This is delivered to your webhook endpoint when Primitive receives an email matching your domain configuration.
703
- *
704
- * This interface was referenced by `EmailReceivedEvent`'s JSON-Schema
705
- * via the `definition` "EmailReceivedEvent".
706
- */
707
692
  type EmailReceivedEvent = EmailReceivedEvent$1;
708
693
  type EventType = EmailReceivedEvent["event"];
709
694
  declare const EventType: {
@@ -814,6 +799,5 @@ interface UnknownEvent {
814
799
  }
815
800
  type KnownWebhookEvent = EmailReceivedEvent;
816
801
  type WebhookEvent = KnownWebhookEvent | UnknownEvent;
817
-
818
802
  //#endregion
819
- export { AuthConfidence as AuthConfidence$1, AuthVerdict as AuthVerdict$1, DkimResult as DkimResult$1, DkimSignature, DmarcPolicy as DmarcPolicy$1, DmarcResult as DmarcResult$1, EmailAddress, EmailAnalysis, EmailAuth, EmailReceivedEvent, EventType as EventType$1, ForwardAnalysis, ForwardOriginalSender, ForwardResult, ForwardResultAttachmentAnalyzed, ForwardResultAttachmentSkipped, ForwardResultInline, ForwardVerdict as ForwardVerdict$1, ForwardVerification, KnownWebhookEvent, ParsedData, ParsedDataComplete, ParsedDataFailed, ParsedError, ParsedStatus as ParsedStatus$1, RawContent, RawContentDownloadOnly, RawContentInline, SpfResult as SpfResult$1, UnknownEvent, ValidateEmailAuthResult, WebhookAttachment, WebhookEvent };
803
+ export { UnknownEvent as A, ParsedDataFailed as C, RawContentDownloadOnly as D, RawContent as E, WebhookAttachment as M, WebhookEvent as N, RawContentInline as O, ParsedDataComplete as S, ParsedStatus as T, ForwardResultInline as _, DmarcPolicy as a, KnownWebhookEvent as b, EmailAnalysis as c, EventType as d, ForwardAnalysis as f, ForwardResultAttachmentSkipped as g, ForwardResultAttachmentAnalyzed as h, DkimSignature as i, ValidateEmailAuthResult as j, SpfResult as k, EmailAuth as l, ForwardResult as m, AuthVerdict as n, DmarcResult as o, ForwardOriginalSender as p, DkimResult as r, EmailAddress as s, AuthConfidence as t, EmailReceivedEvent as u, ForwardVerdict as v, ParsedError as w, ParsedData as x, ForwardVerification as y };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Types for Primitive webhook payloads.
3
+ *
4
+ * AUTO-GENERATED - DO NOT EDIT
5
+ * Run `pnpm generate:types` to regenerate.
6
+ */
7
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Primitive webhook event types derived from the canonical JSON schema.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ export const EventType = {
7
+ EmailReceived: "email.received",
8
+ };
9
+ export const ParsedStatus = {
10
+ Complete: "complete",
11
+ Failed: "failed",
12
+ };
13
+ export const ForwardVerdict = {
14
+ Legit: "legit",
15
+ Unknown: "unknown",
16
+ };
17
+ export const SpfResult = {
18
+ Pass: "pass",
19
+ Fail: "fail",
20
+ Softfail: "softfail",
21
+ Neutral: "neutral",
22
+ None: "none",
23
+ Temperror: "temperror",
24
+ Permerror: "permerror",
25
+ };
26
+ export const DmarcResult = {
27
+ Pass: "pass",
28
+ Fail: "fail",
29
+ None: "none",
30
+ Temperror: "temperror",
31
+ Permerror: "permerror",
32
+ };
33
+ export const DmarcPolicy = {
34
+ Reject: "reject",
35
+ Quarantine: "quarantine",
36
+ None: "none",
37
+ };
38
+ export const DkimResult = {
39
+ Pass: "pass",
40
+ Fail: "fail",
41
+ Temperror: "temperror",
42
+ Permerror: "permerror",
43
+ };
44
+ export const AuthConfidence = {
45
+ High: "high",
46
+ Medium: "medium",
47
+ Low: "low",
48
+ };
49
+ export const AuthVerdict = {
50
+ Legit: "legit",
51
+ Suspicious: "suspicious",
52
+ Unknown: "unknown",
53
+ };
@@ -1,3 +1,4 @@
1
- import { AuthConfidence$1 as AuthConfidence, AuthVerdict$1 as AuthVerdict, DkimResult$1 as DkimResult, DkimSignature, DmarcPolicy$1 as DmarcPolicy, DmarcResult$1 as DmarcResult, EmailAddress, EmailAnalysis, EmailAuth, EmailReceivedEvent, EventType$1 as EventType, ForwardAnalysis, ForwardOriginalSender, ForwardResult, ForwardResultAttachmentAnalyzed, ForwardResultAttachmentSkipped, ForwardResultInline, ForwardVerdict$1 as ForwardVerdict, ForwardVerification, KnownWebhookEvent, ParsedData, ParsedDataComplete, ParsedDataFailed, ParsedError, ParsedStatus$1 as ParsedStatus, RawContent, RawContentDownloadOnly, RawContentInline, SpfResult$1 as SpfResult, UnknownEvent, ValidateEmailAuthResult, WebhookAttachment, WebhookEvent } from "../types-CKFmgitP.js";
2
- import { DecodeRawEmailOptions, GenerateDownloadTokenOptions, HandleWebhookOptions, LEGACY_CONFIRMED_HEADER$1 as LEGACY_CONFIRMED_HEADER, LEGACY_SIGNATURE_HEADER$1 as LEGACY_SIGNATURE_HEADER, PAYLOAD_ERRORS$1 as PAYLOAD_ERRORS, PRIMITIVE_CONFIRMED_HEADER$1 as PRIMITIVE_CONFIRMED_HEADER, PRIMITIVE_SIGNATURE_HEADER$1 as PRIMITIVE_SIGNATURE_HEADER, PrimitiveWebhookError$1 as PrimitiveWebhookError, RAW_EMAIL_ERRORS$1 as RAW_EMAIL_ERRORS, RawEmailDecodeError$1 as RawEmailDecodeError, RawEmailDecodeErrorCode, STANDARD_WEBHOOK_ID_HEADER$1 as STANDARD_WEBHOOK_ID_HEADER, STANDARD_WEBHOOK_SIGNATURE_HEADER$1 as STANDARD_WEBHOOK_SIGNATURE_HEADER, STANDARD_WEBHOOK_TIMESTAMP_HEADER$1 as STANDARD_WEBHOOK_TIMESTAMP_HEADER, SignResult, StandardWebhooksSignResult, StandardWebhooksVerifyOptions, VERIFICATION_ERRORS$1 as VERIFICATION_ERRORS, VerifyDownloadTokenOptions, VerifyDownloadTokenResult, VerifyOptions, WEBHOOK_VERSION$1 as WEBHOOK_VERSION, WebhookErrorCode, WebhookHeaders, WebhookPayloadError$1 as WebhookPayloadError, WebhookPayloadErrorCode, WebhookValidationError$1 as WebhookValidationError, WebhookValidationErrorCode, WebhookVerificationError$1 as WebhookVerificationError, WebhookVerificationErrorCode, confirmedHeaders$1 as confirmedHeaders, decodeRawEmail$1 as decodeRawEmail, emailReceivedEventJsonSchema$1 as emailReceivedEventJsonSchema, generateDownloadToken$1 as generateDownloadToken, getDownloadTimeRemaining$1 as getDownloadTimeRemaining, handleWebhook$1 as handleWebhook, isDownloadExpired$1 as isDownloadExpired, isEmailReceivedEvent$1 as isEmailReceivedEvent, isRawIncluded$1 as isRawIncluded, parseWebhookEvent$1 as parseWebhookEvent, safeValidateEmailReceivedEvent$1 as safeValidateEmailReceivedEvent, signStandardWebhooksPayload$1 as signStandardWebhooksPayload, signWebhookPayload$1 as signWebhookPayload, validateEmailAuth$1 as validateEmailAuth, validateEmailReceivedEvent$1 as validateEmailReceivedEvent, verifyDownloadToken$1 as verifyDownloadToken, verifyRawEmailDownload$1 as verifyRawEmailDownload, verifyStandardWebhooksSignature$1 as verifyStandardWebhooksSignature, verifyWebhookSignature$1 as verifyWebhookSignature } from "../index-DLmAI4UQ.js";
3
- export { AuthConfidence, AuthVerdict, DecodeRawEmailOptions, DkimResult, DkimSignature, DmarcPolicy, DmarcResult, EmailAddress, EmailAnalysis, EmailAuth, EmailReceivedEvent, EventType, ForwardAnalysis, ForwardOriginalSender, ForwardResult, ForwardResultAttachmentAnalyzed, ForwardResultAttachmentSkipped, ForwardResultInline, ForwardVerdict, ForwardVerification, GenerateDownloadTokenOptions, HandleWebhookOptions, KnownWebhookEvent, LEGACY_CONFIRMED_HEADER, LEGACY_SIGNATURE_HEADER, PAYLOAD_ERRORS, PRIMITIVE_CONFIRMED_HEADER, PRIMITIVE_SIGNATURE_HEADER, ParsedData, ParsedDataComplete, ParsedDataFailed, ParsedError, ParsedStatus, PrimitiveWebhookError, RAW_EMAIL_ERRORS, RawContent, RawContentDownloadOnly, RawContentInline, RawEmailDecodeError, RawEmailDecodeErrorCode, STANDARD_WEBHOOK_ID_HEADER, STANDARD_WEBHOOK_SIGNATURE_HEADER, STANDARD_WEBHOOK_TIMESTAMP_HEADER, SignResult, SpfResult, StandardWebhooksSignResult, StandardWebhooksVerifyOptions, UnknownEvent, VERIFICATION_ERRORS, ValidateEmailAuthResult, VerifyDownloadTokenOptions, VerifyDownloadTokenResult, VerifyOptions, WEBHOOK_VERSION, WebhookAttachment, WebhookErrorCode, WebhookEvent, WebhookHeaders, WebhookPayloadError, WebhookPayloadErrorCode, WebhookValidationError, WebhookValidationErrorCode, WebhookVerificationError, WebhookVerificationErrorCode, confirmedHeaders, decodeRawEmail, emailReceivedEventJsonSchema, generateDownloadToken, getDownloadTimeRemaining, handleWebhook, isDownloadExpired, isEmailReceivedEvent, isRawIncluded, parseWebhookEvent, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature };
1
+ import { A as UnknownEvent, C as ParsedDataFailed, D as RawContentDownloadOnly, E as RawContent, M as WebhookAttachment, N as WebhookEvent, O as RawContentInline, S as ParsedDataComplete, T as ParsedStatus, _ as ForwardResultInline, a as DmarcPolicy, b as KnownWebhookEvent, c as EmailAnalysis, d as EventType, f as ForwardAnalysis, g as ForwardResultAttachmentSkipped, h as ForwardResultAttachmentAnalyzed, i as DkimSignature, j as ValidateEmailAuthResult, k as SpfResult, l as EmailAuth, m as ForwardResult, n as AuthVerdict, o as DmarcResult, p as ForwardOriginalSender, r as DkimResult, s as EmailAddress, t as AuthConfidence, u as EmailReceivedEvent, v as ForwardVerdict, w as ParsedError, x as ParsedData, y as ForwardVerification } from "../types-9vXGZjPd.js";
2
+ import { a as buildReplySubject, c as parseHeaderAddress, i as buildForwardSubject, n as ReceivedEmailAddress, o as formatAddress, r as ReceivedEmailThread, s as normalizeReceivedEmail, t as ReceivedEmail } from "../received-email-DNjpq_Wt.js";
3
+ import { A as VerifyOptions, B as PAYLOAD_ERRORS, C as signStandardWebhooksPayload, D as PRIMITIVE_CONFIRMED_HEADER, E as LEGACY_SIGNATURE_HEADER, F as VerifyDownloadTokenResult, G as VERIFICATION_ERRORS, H as RAW_EMAIL_ERRORS, I as generateDownloadToken, J as WebhookPayloadErrorCode, K as WebhookErrorCode, L as verifyDownloadToken, M as verifyWebhookSignature, N as GenerateDownloadTokenOptions, O as PRIMITIVE_SIGNATURE_HEADER, P as VerifyDownloadTokenOptions, Q as WebhookVerificationErrorCode, R as safeValidateEmailReceivedEvent, S as StandardWebhooksVerifyOptions, T as LEGACY_CONFIRMED_HEADER, U as RawEmailDecodeError, V as PrimitiveWebhookError, W as RawEmailDecodeErrorCode, X as WebhookValidationErrorCode, Y as WebhookValidationError, Z as WebhookVerificationError, _ as emailReceivedEventJsonSchema, a as confirmedHeaders, b as STANDARD_WEBHOOK_TIMESTAMP_HEADER, c as handleWebhook, d as isRawIncluded, f as parseWebhookEvent, g as validateEmailAuth, h as WEBHOOK_VERSION, i as WebhookHeaders, j as signWebhookPayload, k as SignResult, l as isDownloadExpired, m as verifyRawEmailDownload, n as HandleWebhookOptions, o as decodeRawEmail, p as receive, q as WebhookPayloadError, r as ReceiveRequestOptions, s as getDownloadTimeRemaining, t as DecodeRawEmailOptions, u as isEmailReceivedEvent, v as STANDARD_WEBHOOK_ID_HEADER, w as verifyStandardWebhooksSignature, x as StandardWebhooksSignResult, y as STANDARD_WEBHOOK_SIGNATURE_HEADER, z as validateEmailReceivedEvent } from "../index-CbEivn3S.js";
4
+ export { AuthConfidence, AuthVerdict, DecodeRawEmailOptions, DkimResult, DkimSignature, DmarcPolicy, DmarcResult, EmailAddress, EmailAnalysis, EmailAuth, EmailReceivedEvent, EventType, ForwardAnalysis, ForwardOriginalSender, ForwardResult, ForwardResultAttachmentAnalyzed, ForwardResultAttachmentSkipped, ForwardResultInline, ForwardVerdict, ForwardVerification, GenerateDownloadTokenOptions, HandleWebhookOptions, KnownWebhookEvent, LEGACY_CONFIRMED_HEADER, LEGACY_SIGNATURE_HEADER, PAYLOAD_ERRORS, PRIMITIVE_CONFIRMED_HEADER, PRIMITIVE_SIGNATURE_HEADER, ParsedData, ParsedDataComplete, ParsedDataFailed, ParsedError, ParsedStatus, PrimitiveWebhookError, RAW_EMAIL_ERRORS, RawContent, RawContentDownloadOnly, RawContentInline, RawEmailDecodeError, RawEmailDecodeErrorCode, ReceiveRequestOptions, ReceivedEmail, ReceivedEmailAddress, ReceivedEmailThread, STANDARD_WEBHOOK_ID_HEADER, STANDARD_WEBHOOK_SIGNATURE_HEADER, STANDARD_WEBHOOK_TIMESTAMP_HEADER, SignResult, SpfResult, StandardWebhooksSignResult, StandardWebhooksVerifyOptions, UnknownEvent, VERIFICATION_ERRORS, ValidateEmailAuthResult, VerifyDownloadTokenOptions, VerifyDownloadTokenResult, VerifyOptions, WEBHOOK_VERSION, WebhookAttachment, WebhookErrorCode, WebhookEvent, WebhookHeaders, WebhookPayloadError, WebhookPayloadErrorCode, WebhookValidationError, WebhookValidationErrorCode, WebhookVerificationError, WebhookVerificationErrorCode, buildForwardSubject, buildReplySubject, confirmedHeaders, decodeRawEmail, emailReceivedEventJsonSchema, formatAddress, generateDownloadToken, getDownloadTimeRemaining, handleWebhook, isDownloadExpired, isEmailReceivedEvent, isRawIncluded, normalizeReceivedEmail, parseHeaderAddress, parseWebhookEvent, receive, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature };
@@ -1,3 +1,3 @@
1
- import { AuthConfidence, AuthVerdict, DkimResult, DmarcPolicy, DmarcResult, EventType, ForwardVerdict, LEGACY_CONFIRMED_HEADER, LEGACY_SIGNATURE_HEADER, PAYLOAD_ERRORS, PRIMITIVE_CONFIRMED_HEADER, PRIMITIVE_SIGNATURE_HEADER, ParsedStatus, PrimitiveWebhookError, RAW_EMAIL_ERRORS, RawEmailDecodeError, STANDARD_WEBHOOK_ID_HEADER, STANDARD_WEBHOOK_SIGNATURE_HEADER, STANDARD_WEBHOOK_TIMESTAMP_HEADER, SpfResult, VERIFICATION_ERRORS, WEBHOOK_VERSION, WebhookPayloadError, WebhookValidationError, WebhookVerificationError, confirmedHeaders, decodeRawEmail, emailReceivedEventJsonSchema, generateDownloadToken, getDownloadTimeRemaining, handleWebhook, isDownloadExpired, isEmailReceivedEvent, isRawIncluded, parseWebhookEvent, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature } from "../webhook-COe5N_Uj.js";
2
-
3
- export { AuthConfidence, AuthVerdict, DkimResult, DmarcPolicy, DmarcResult, EventType, ForwardVerdict, LEGACY_CONFIRMED_HEADER, LEGACY_SIGNATURE_HEADER, PAYLOAD_ERRORS, PRIMITIVE_CONFIRMED_HEADER, PRIMITIVE_SIGNATURE_HEADER, ParsedStatus, PrimitiveWebhookError, RAW_EMAIL_ERRORS, RawEmailDecodeError, STANDARD_WEBHOOK_ID_HEADER, STANDARD_WEBHOOK_SIGNATURE_HEADER, STANDARD_WEBHOOK_TIMESTAMP_HEADER, SpfResult, VERIFICATION_ERRORS, WEBHOOK_VERSION, WebhookPayloadError, WebhookValidationError, WebhookVerificationError, confirmedHeaders, decodeRawEmail, emailReceivedEventJsonSchema, generateDownloadToken, getDownloadTimeRemaining, handleWebhook, isDownloadExpired, isEmailReceivedEvent, isRawIncluded, parseWebhookEvent, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature };
1
+ import { a as parseHeaderAddress, i as normalizeReceivedEmail, n as buildReplySubject, r as formatAddress, t as buildForwardSubject } from "../received-email-D6tKtWwW.js";
2
+ import { A as PRIMITIVE_CONFIRMED_HEADER, B as RAW_EMAIL_ERRORS, C as STANDARD_WEBHOOK_ID_HEADER, D as verifyStandardWebhooksSignature, E as signStandardWebhooksPayload, F as verifyDownloadToken, G as WebhookVerificationError, H as VERIFICATION_ERRORS, I as safeValidateEmailReceivedEvent, L as validateEmailReceivedEvent, M as signWebhookPayload, N as verifyWebhookSignature, O as LEGACY_CONFIRMED_HEADER, P as generateDownloadToken, R as PAYLOAD_ERRORS, S as emailReceivedEventJsonSchema, T as STANDARD_WEBHOOK_TIMESTAMP_HEADER, U as WebhookPayloadError, V as RawEmailDecodeError, W as WebhookValidationError, _ as DmarcResult, a as isDownloadExpired, b as ParsedStatus, c as parseWebhookEvent, d as WEBHOOK_VERSION, f as validateEmailAuth, g as DmarcPolicy, h as DkimResult, i as handleWebhook, j as PRIMITIVE_SIGNATURE_HEADER, k as LEGACY_SIGNATURE_HEADER, l as receive, m as AuthVerdict, n as decodeRawEmail, o as isEmailReceivedEvent, p as AuthConfidence, r as getDownloadTimeRemaining, s as isRawIncluded, t as confirmedHeaders, u as verifyRawEmailDownload, v as EventType, w as STANDARD_WEBHOOK_SIGNATURE_HEADER, x as SpfResult, y as ForwardVerdict, z as PrimitiveWebhookError } from "../webhook-zkN4wUTs.js";
3
+ export { AuthConfidence, AuthVerdict, DkimResult, DmarcPolicy, DmarcResult, EventType, ForwardVerdict, LEGACY_CONFIRMED_HEADER, LEGACY_SIGNATURE_HEADER, PAYLOAD_ERRORS, PRIMITIVE_CONFIRMED_HEADER, PRIMITIVE_SIGNATURE_HEADER, ParsedStatus, PrimitiveWebhookError, RAW_EMAIL_ERRORS, RawEmailDecodeError, STANDARD_WEBHOOK_ID_HEADER, STANDARD_WEBHOOK_SIGNATURE_HEADER, STANDARD_WEBHOOK_TIMESTAMP_HEADER, SpfResult, VERIFICATION_ERRORS, WEBHOOK_VERSION, WebhookPayloadError, WebhookValidationError, WebhookVerificationError, buildForwardSubject, buildReplySubject, confirmedHeaders, decodeRawEmail, emailReceivedEventJsonSchema, formatAddress, generateDownloadToken, getDownloadTimeRemaining, handleWebhook, isDownloadExpired, isEmailReceivedEvent, isRawIncluded, normalizeReceivedEmail, parseHeaderAddress, parseWebhookEvent, receive, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature };
@@ -0,0 +1,82 @@
1
+ import { parseFromHeaderLoose } from "../parser/address-parser.js";
2
+ const REPLY_PREFIX_RE = /^re\s*:/i;
3
+ const FORWARD_PREFIX_RE = /^(fwd?|fw)\s*:/i;
4
+ export function normalizeReceivedEmail(event) {
5
+ const receivedBy = event.email.smtp.rcpt_to[0];
6
+ if (!receivedBy) {
7
+ throw new Error("email.smtp.rcpt_to must contain at least one recipient");
8
+ }
9
+ const sender = parseHeaderAddress(event.email.headers.from) ?? {
10
+ address: event.email.smtp.mail_from.trim().toLowerCase(),
11
+ name: null,
12
+ };
13
+ const replyTarget = firstStructuredAddress(event.email.parsed.reply_to) ?? sender;
14
+ const subject = event.email.headers.subject ?? null;
15
+ const references = event.email.parsed.references ?? [];
16
+ const messageId = event.email.headers.message_id ?? null;
17
+ return {
18
+ id: event.email.id,
19
+ eventId: event.id,
20
+ receivedAt: event.email.received_at,
21
+ sender,
22
+ replyTarget,
23
+ receivedBy,
24
+ receivedByAll: [...event.email.smtp.rcpt_to],
25
+ subject,
26
+ replySubject: buildReplySubject(subject),
27
+ forwardSubject: buildForwardSubject(subject),
28
+ text: event.email.parsed.body_text ?? null,
29
+ thread: {
30
+ messageId,
31
+ inReplyTo: event.email.parsed.in_reply_to ?? [],
32
+ references,
33
+ },
34
+ attachments: event.email.parsed.attachments ?? [],
35
+ auth: event.email.auth,
36
+ analysis: event.email.analysis,
37
+ raw: event,
38
+ };
39
+ }
40
+ export function buildReplySubject(subject) {
41
+ const trimmed = subject?.trim() ?? "";
42
+ if (trimmed.length === 0) {
43
+ return "Re:";
44
+ }
45
+ return REPLY_PREFIX_RE.test(trimmed) ? trimmed : `Re: ${trimmed}`;
46
+ }
47
+ export function buildForwardSubject(subject) {
48
+ const trimmed = subject?.trim() ?? "";
49
+ if (trimmed.length === 0) {
50
+ return "Fwd:";
51
+ }
52
+ return FORWARD_PREFIX_RE.test(trimmed) ? trimmed : `Fwd: ${trimmed}`;
53
+ }
54
+ export function formatAddress(address) {
55
+ return address.name
56
+ ? `${address.name} <${address.address}>`
57
+ : address.address;
58
+ }
59
+ function firstStructuredAddress(addresses) {
60
+ const address = addresses?.[0];
61
+ if (!address) {
62
+ return null;
63
+ }
64
+ return {
65
+ address: address.address.trim().toLowerCase(),
66
+ name: address.name ?? null,
67
+ };
68
+ }
69
+ // Lenient about quirky headers (unquoted commas in display names, missing
70
+ // closing angle brackets) but strict about the resulting address: it must
71
+ // validate as an email, otherwise the normalizer falls back to the SMTP
72
+ // envelope sender. Exported so cross-SDK fixtures can exercise it directly.
73
+ export function parseHeaderAddress(value) {
74
+ const parsed = parseFromHeaderLoose(value);
75
+ if (!parsed) {
76
+ return null;
77
+ }
78
+ return {
79
+ address: parsed.address,
80
+ name: parsed.name?.trim() || null,
81
+ };
82
+ }