@primitivedotdev/sdk 0.7.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.
- package/README.md +114 -210
- package/dist/address-parser-CfPHs3mE.js +113 -0
- package/dist/api/generated/index.js +1 -1
- package/dist/api/generated/sdk.gen.js +17 -0
- package/dist/api/index.d.ts +4 -1877
- package/dist/api/index.js +255 -0
- package/dist/api-BH8PnmHs.js +1338 -0
- package/dist/contract/index.d.ts +3 -2
- package/dist/contract/index.js +3 -1
- package/dist/{index-DLmAI4UQ.d.ts → index-CuuP1JkG.d.ts} +9 -2
- package/dist/index-D9lanVFt.d.ts +2145 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +13 -2
- package/dist/openapi/openapi.generated.js +413 -1
- package/dist/openapi/operations.generated.js +16 -0
- package/dist/parser/address-parser.js +129 -0
- package/dist/parser/index.d.ts +1 -1
- package/dist/parser/index.js +1 -105
- package/dist/received-email-C67Z7Dha.d.ts +36 -0
- package/dist/received-email-Q6Cha3wc.js +71 -0
- 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 +4 -2
- package/dist/webhook/received-email.js +82 -0
- package/dist/{webhook-COe5N_Uj.js → webhook-2TALcBQz.js} +16 -1
- package/oclif.manifest.json +47 -1
- package/package.json +4 -1
- /package/dist/{types-CKFmgitP.d.ts → types-CIOzt1FY.d.ts} +0 -0
|
@@ -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
package/dist/parser/index.js
CHANGED
|
@@ -1,114 +1,10 @@
|
|
|
1
|
+
import { parseFromHeader, parseFromHeaderLoose } from "../address-parser-CfPHs3mE.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
7
|
|
|
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
8
|
//#region src/parser/attachment-bundler.ts
|
|
113
9
|
function appendTarEntry(archive, name, content) {
|
|
114
10
|
return new Promise((resolve, reject) => {
|
|
@@ -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 };
|
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
|
+
};
|
package/dist/webhook/index.d.ts
CHANGED
|
@@ -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-
|
|
2
|
-
import {
|
|
3
|
-
|
|
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 };
|
package/dist/webhook/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import
|
|
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 };
|
package/oclif.manifest.json
CHANGED
|
@@ -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.
|
|
1312
|
+
"version": "0.8.0"
|
|
1267
1313
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitivedotdev/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Official Primitive Node.js SDK — webhook, api, openapi, contract, and parser modules",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -68,6 +68,9 @@
|
|
|
68
68
|
"emails": {
|
|
69
69
|
"description": "List, inspect, and manage received emails"
|
|
70
70
|
},
|
|
71
|
+
"sending": {
|
|
72
|
+
"description": "Send outbound emails through the Primitive API"
|
|
73
|
+
},
|
|
71
74
|
"endpoints": {
|
|
72
75
|
"description": "Manage webhook endpoints that receive email events"
|
|
73
76
|
},
|
|
File without changes
|