@primitivedotdev/sdk 0.6.0 → 0.8.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.
@@ -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
+ }
@@ -1,5 +1,105 @@
1
- import { EmailAddress, ParsedDataComplete, WebhookAttachment } from "../types-CKFmgitP.js";
1
+ import { EmailAddress, ParsedDataComplete, WebhookAttachment } from "../types-CIOzt1FY.js";
2
2
 
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
+ /**
16
+ * A validated RFC 5322 address. Returned by the strict parser, which
17
+ * deliberately does not expose a display name.
18
+ *
19
+ * `address` is normalized to lowercase. Both the local-part and the
20
+ * domain are lowercased: RFC 5321 §2.4 permits case-sensitive local-
21
+ * parts, but every consumer mailbox in practice treats them as
22
+ * case-insensitive, and a case-sensitive grant key would split
23
+ * `Bob@x.com` from `bob@x.com` into separate rows and defeat the
24
+ * primary-key index on lookup.
25
+ */
26
+ interface ValidatedAddress {
27
+ address: string;
28
+ }
29
+ /**
30
+ * A parsed RFC 5322 address with its display name. Returned by the
31
+ * loose parser for display-only call sites.
32
+ *
33
+ * `address` is lowercased on the same rationale as
34
+ * {@link ValidatedAddress}. The display name is preserved as provided
35
+ * (after addressparser's quote / encoded-word handling), or null if the
36
+ * header had no display name. Names from the loose parser are NOT
37
+ * trustworthy for downstream mail building: addressparser's recovery
38
+ * mode can fold trailing tokens or a second bracketed address into the
39
+ * name field. Treat as opaque text, sanitize before re-emitting.
40
+ */
41
+ interface ParsedAddress {
42
+ address: string;
43
+ name: string | null;
44
+ }
45
+ /**
46
+ * Reason a strict From-header parse rejected the input. Stable enum so
47
+ * callers can branch on the reason without parsing message text.
48
+ */
49
+ type ParseFromHeaderFailureReason = "empty" | "too_long" | "multiple_addresses" | "group_syntax" | "invalid_address";
50
+ type ParseFromHeaderResult = {
51
+ ok: true;
52
+ value: ValidatedAddress;
53
+ } | {
54
+ ok: false;
55
+ reason: ParseFromHeaderFailureReason;
56
+ };
57
+ /**
58
+ * Strict parser for RFC 5322 From-style headers in security-bearing
59
+ * contexts (allowlist gates, permission grants).
60
+ *
61
+ * Rejects, without falling back to a "best guess":
62
+ * - empty / whitespace-only input
63
+ * - inputs longer than RFC 5322's 998-octet line limit
64
+ * - multi-address From (RFC 5322 allows it but it is vanishingly
65
+ * rare and ambiguous as an identity)
66
+ * - group syntax ("Friends: a@b.com, c@d.com;")
67
+ * - any address that fails validator's isEmail check with our chosen
68
+ * options. That covers per-part length limits, dot-atom rules,
69
+ * hostname-label rules, TLD requirement, and other RFC 5321/5322
70
+ * conformance checks.
71
+ *
72
+ * Returns ONLY the validated address, with no display name. Strict
73
+ * exists for gating decisions, where the address is the security-
74
+ * bearing field. Display names from addressparser are not trustworthy
75
+ * here: weird inputs like `Name <user@x.com> <attacker@y.com>` get
76
+ * parsed as a single entry whose `name` silently includes the second
77
+ * address. Surfacing that as a "parsed name" would invite downstream
78
+ * misuse, so we drop it. If you need the name, call
79
+ * {@link parseFromHeaderLoose} alongside (it returns null on failure
80
+ * anyway, so you can still gate on strict's Result).
81
+ *
82
+ * Returns a typed Result so callers can map the failure reason to
83
+ * stable error codes without inspecting message text.
84
+ */
85
+ declare function parseFromHeader(header: string | null | undefined): ParseFromHeaderResult;
86
+ /**
87
+ * Lenient parser for display-only call sites (inbox card "from",
88
+ * log lines, debugging). Returns the first parseable address with its
89
+ * display name, or null.
90
+ *
91
+ * Differences from {@link parseFromHeader}:
92
+ * - Multi-address From returns the first address instead of rejecting
93
+ * - Group syntax is flattened into its member addresses
94
+ * - Returns null instead of a typed reason on failure
95
+ * - Includes the parsed display name in the result
96
+ *
97
+ * Do not use for permission gates or any decision that grants access.
98
+ * That is what {@link parseFromHeader} is for. Names returned here can
99
+ * include addressparser's recovery output (trailing tokens, garbage
100
+ * before the address); treat as opaque text for display.
101
+ */
102
+ declare function parseFromHeaderLoose(header: string | null | undefined): ParsedAddress | null; //#endregion
3
103
  //#region src/parser/attachment-parser.d.ts
4
104
  interface ParsedAttachment {
5
105
  id: string;
@@ -61,7 +161,9 @@ declare function sha256Hex(buffer: Buffer): string;
61
161
  * Sanitize a filename for safe use in Content-Disposition headers.
62
162
  * Prevents path traversal, removes control characters, enforces length limits.
63
163
  */
64
- declare function sanitizeFilename(filename: string | null, partIndex: number): string; //#endregion
164
+ declare function sanitizeFilename(filename: string | null, partIndex: number): string;
165
+
166
+ //#endregion
65
167
  //#region src/parser/attachment-bundler.d.ts
66
168
  /**
67
169
  * Result of bundling attachments into a tar.gz archive
@@ -187,4 +289,4 @@ declare function toCanonicalHeaders(parsed: ParsedEmailWithAttachments): {
187
289
  declare function sanitizeHtml(html: string): string;
188
290
 
189
291
  //#endregion
190
- export { AttachmentMetadata, BundleResult, ParsedAttachment, ParsedEmail, ParsedEmailWithAttachments, attachmentMetadataToWebhookAttachments, bundleAttachments, extractAttachmentMetadata, getAttachmentsStorageKey, normalizeContentType, parseEmail, parseEmailWithAttachments, sanitizeFilename, sanitizeHtml, sha256Hex, toCanonicalHeaders, toParsedDataComplete, toWebhookAttachments };
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 };
@@ -1,3 +1,4 @@
1
+ import { parseFromHeader, parseFromHeaderLoose } from "../address-parser-CfPHs3mE.js";
1
2
  import { createHash } from "node:crypto";
2
3
  import { createGzip } from "node:zlib";
3
4
  import { pack } from "tar-stream";
@@ -557,4 +558,4 @@ function requireNonEmptyHeader(value, headerName) {
557
558
  }
558
559
 
559
560
  //#endregion
560
- export { attachmentMetadataToWebhookAttachments, bundleAttachments, extractAttachmentMetadata, getAttachmentsStorageKey, normalizeContentType, parseEmail, parseEmailWithAttachments, sanitizeFilename, sanitizeHtml, sha256Hex, toCanonicalHeaders, toParsedDataComplete, toWebhookAttachments };
561
+ export { attachmentMetadataToWebhookAttachments, bundleAttachments, extractAttachmentMetadata, getAttachmentsStorageKey, normalizeContentType, parseEmail, parseEmailWithAttachments, parseFromHeader, parseFromHeaderLoose, sanitizeFilename, sanitizeHtml, sha256Hex, toCanonicalHeaders, toParsedDataComplete, toWebhookAttachments };
@@ -0,0 +1,36 @@
1
+ import { EmailAnalysis, EmailAuth, EmailReceivedEvent, WebhookAttachment } from "./types-CIOzt1FY.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; //#endregion
36
+ export { ReceivedEmail, ReceivedEmailAddress, ReceivedEmailThread, buildForwardSubject as buildForwardSubject$1, buildReplySubject as buildReplySubject$1, formatAddress as formatAddress$1, normalizeReceivedEmail as normalizeReceivedEmail$1, parseHeaderAddress as parseHeaderAddress$1 };
@@ -0,0 +1,71 @@
1
+ import { parseFromHeaderLoose } from "./address-parser-CfPHs3mE.js";
2
+
3
+ //#region src/webhook/received-email.ts
4
+ const REPLY_PREFIX_RE = /^re\s*:/i;
5
+ const FORWARD_PREFIX_RE = /^(fwd?|fw)\s*:/i;
6
+ function normalizeReceivedEmail(event) {
7
+ const receivedBy = event.email.smtp.rcpt_to[0];
8
+ if (!receivedBy) throw new Error("email.smtp.rcpt_to must contain at least one recipient");
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
+ function buildReplySubject(subject) {
41
+ const trimmed = subject?.trim() ?? "";
42
+ if (trimmed.length === 0) return "Re:";
43
+ return REPLY_PREFIX_RE.test(trimmed) ? trimmed : `Re: ${trimmed}`;
44
+ }
45
+ function buildForwardSubject(subject) {
46
+ const trimmed = subject?.trim() ?? "";
47
+ if (trimmed.length === 0) return "Fwd:";
48
+ return FORWARD_PREFIX_RE.test(trimmed) ? trimmed : `Fwd: ${trimmed}`;
49
+ }
50
+ function formatAddress(address) {
51
+ return address.name ? `${address.name} <${address.address}>` : address.address;
52
+ }
53
+ function firstStructuredAddress(addresses) {
54
+ const address = addresses?.[0];
55
+ if (!address) return null;
56
+ return {
57
+ address: address.address.trim().toLowerCase(),
58
+ name: address.name ?? null
59
+ };
60
+ }
61
+ function parseHeaderAddress(value) {
62
+ const parsed = parseFromHeaderLoose(value);
63
+ if (!parsed) return null;
64
+ return {
65
+ address: parsed.address,
66
+ name: parsed.name?.trim() || null
67
+ };
68
+ }
69
+
70
+ //#endregion
71
+ export { buildForwardSubject, buildReplySubject, formatAddress, normalizeReceivedEmail, parseHeaderAddress };
@@ -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 { 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-CIOzt1FY.js";
2
+ import { ReceivedEmail, ReceivedEmailAddress, ReceivedEmailThread, buildForwardSubject$1 as buildForwardSubject, buildReplySubject$1 as buildReplySubject, formatAddress$1 as formatAddress, normalizeReceivedEmail$1 as normalizeReceivedEmail, parseHeaderAddress$1 as parseHeaderAddress } from "../received-email-C67Z7Dha.js";
3
+ 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, ReceiveRequestOptions, 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, receive$1 as receive, 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-CuuP1JkG.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,5 @@
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";
1
+ import "../address-parser-CfPHs3mE.js";
2
+ import { buildForwardSubject, buildReplySubject, formatAddress, normalizeReceivedEmail, parseHeaderAddress } from "../received-email-Q6Cha3wc.js";
3
+ 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, receive, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature } from "../webhook-2TALcBQz.js";
2
4
 
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 };
5
+ 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
+ }
@@ -1,3 +1,4 @@
1
+ import { normalizeReceivedEmail } from "./received-email-Q6Cha3wc.js";
1
2
  import { createHash, createHmac, timingSafeEqual } from "node:crypto";
2
3
 
3
4
  //#region src/generated/email-received-event.validator.generated.ts
@@ -7829,6 +7830,20 @@ function handleWebhook(options) {
7829
7830
  const parsed = parseJsonBody(body);
7830
7831
  return validateEmailReceivedEvent(parsed);
7831
7832
  }
7833
+ function receive(input, options) {
7834
+ if (input instanceof Request) return receiveFromRequest(input, options);
7835
+ return normalizeReceivedEmail(handleWebhook(input));
7836
+ }
7837
+ async function receiveFromRequest(request, options) {
7838
+ if (!options?.secret) throw new WebhookVerificationError("MISSING_SECRET", "Webhook secret is required but was empty or not provided");
7839
+ const body = Buffer.from(await request.arrayBuffer());
7840
+ return normalizeReceivedEmail(handleWebhook({
7841
+ body,
7842
+ headers: request.headers,
7843
+ secret: options.secret,
7844
+ toleranceSeconds: options.toleranceSeconds
7845
+ }));
7846
+ }
7832
7847
  /**
7833
7848
  * Returns headers for the optional "content discard" feature.
7834
7849
  *
@@ -8003,4 +8018,4 @@ function verifyRawEmailDownload(downloaded, event) {
8003
8018
  }
8004
8019
 
8005
8020
  //#endregion
8006
- 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 };
8021
+ 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, receive, safeValidateEmailReceivedEvent, signStandardWebhooksPayload, signWebhookPayload, validateEmailAuth, validateEmailReceivedEvent, verifyDownloadToken, verifyRawEmailDownload, verifyStandardWebhooksSignature, verifyWebhookSignature };
@@ -1136,6 +1136,52 @@
1136
1136
  "summary": "Update a filter rule",
1137
1137
  "enableJsonFlag": false
1138
1138
  },
1139
+ "sending:send-email": {
1140
+ "aliases": [],
1141
+ "args": {},
1142
+ "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",
1143
+ "flags": {
1144
+ "api-key": {
1145
+ "description": "Primitive API key (defaults to PRIMITIVE_API_KEY)",
1146
+ "env": "PRIMITIVE_API_KEY",
1147
+ "name": "api-key",
1148
+ "hasDynamicHelp": false,
1149
+ "multiple": false,
1150
+ "type": "option"
1151
+ },
1152
+ "base-url": {
1153
+ "description": "API base URL (defaults to PRIMITIVE_API_URL or production)",
1154
+ "env": "PRIMITIVE_API_URL",
1155
+ "name": "base-url",
1156
+ "hasDynamicHelp": false,
1157
+ "multiple": false,
1158
+ "type": "option"
1159
+ },
1160
+ "body": {
1161
+ "description": "JSON request body",
1162
+ "name": "body",
1163
+ "hasDynamicHelp": false,
1164
+ "multiple": false,
1165
+ "type": "option"
1166
+ },
1167
+ "body-file": {
1168
+ "description": "Path to a JSON file used as the request body",
1169
+ "name": "body-file",
1170
+ "hasDynamicHelp": false,
1171
+ "multiple": false,
1172
+ "type": "option"
1173
+ }
1174
+ },
1175
+ "hasDynamicHelp": false,
1176
+ "hiddenAliases": [],
1177
+ "id": "sending:send-email",
1178
+ "pluginAlias": "@primitivedotdev/sdk",
1179
+ "pluginName": "@primitivedotdev/sdk",
1180
+ "pluginType": "core",
1181
+ "strict": true,
1182
+ "summary": "Send outbound email",
1183
+ "enableJsonFlag": false
1184
+ },
1139
1185
  "webhook-deliveries:list-deliveries": {
1140
1186
  "aliases": [],
1141
1187
  "args": {},
@@ -1263,5 +1309,5 @@
1263
1309
  "enableJsonFlag": false
1264
1310
  }
1265
1311
  },
1266
- "version": "0.6.0"
1312
+ "version": "0.8.0"
1267
1313
  }