@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.
- 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 +105 -3
- package/dist/parser/index.js +2 -1
- 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 +10 -3
- /package/dist/{types-CKFmgitP.d.ts → types-CIOzt1FY.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Official Primitive Node.js SDK.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The default root import is intentionally small and centered on email
|
|
6
|
+
automation:
|
|
6
7
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
8
|
+
- `primitive.receive(...)`
|
|
9
|
+
- `primitive.client(...)`
|
|
10
|
+
- `client.send(...)`
|
|
11
|
+
- `client.reply(...)`
|
|
12
|
+
- `client.forward(...)`
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Advanced webhook helpers, generated API operations, OpenAPI exports, contract
|
|
15
|
+
tooling, raw MIME parsing, and the CLI still exist as named exports or subpath
|
|
16
|
+
imports.
|
|
16
17
|
|
|
17
18
|
## Requirements
|
|
18
19
|
|
|
@@ -24,207 +25,154 @@ It also publishes the `primitive` CLI bin from the same package.
|
|
|
24
25
|
npm install @primitivedotdev/sdk
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
### Webhook
|
|
28
|
+
## Basic usage
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
### Receive and reply in a Next.js route
|
|
32
31
|
|
|
33
32
|
```ts
|
|
34
|
-
import
|
|
35
|
-
|
|
36
|
-
app.post("/webhooks/email", express.raw({ type: "application/json" }), (req, res) => {
|
|
37
|
-
try {
|
|
38
|
-
const event = handleWebhook({
|
|
39
|
-
body: req.body,
|
|
40
|
-
headers: req.headers,
|
|
41
|
-
secret: process.env.PRIMITIVE_WEBHOOK_SECRET!,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
console.log("Email from:", event.email.headers.from);
|
|
45
|
-
console.log("Subject:", event.email.headers.subject);
|
|
46
|
-
|
|
47
|
-
res.json({ received: true });
|
|
48
|
-
} catch (error) {
|
|
49
|
-
if (error instanceof PrimitiveWebhookError) {
|
|
50
|
-
return res.status(400).json({ error: error.code, message: error.message });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
```
|
|
33
|
+
import primitive from "@primitivedotdev/sdk";
|
|
57
34
|
|
|
58
|
-
|
|
35
|
+
export const runtime = "nodejs";
|
|
36
|
+
export const maxDuration = 300;
|
|
59
37
|
|
|
60
|
-
|
|
38
|
+
const client = primitive.client({
|
|
39
|
+
apiKey: process.env.PRIMITIVE_API_KEY!,
|
|
40
|
+
});
|
|
61
41
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- `verifyWebhookSignature(options)`
|
|
67
|
-
- `validateEmailAuth(auth)`
|
|
68
|
-
- `emailReceivedEventJsonSchema`
|
|
69
|
-
- `WEBHOOK_VERSION`
|
|
70
|
-
- webhook error classes and webhook types
|
|
42
|
+
export async function POST(req: Request) {
|
|
43
|
+
const email = await primitive.receive(req, {
|
|
44
|
+
secret: process.env.PRIMITIVE_WEBHOOK_SECRET!,
|
|
45
|
+
});
|
|
71
46
|
|
|
72
|
-
|
|
47
|
+
await client.reply(email, "Thank you for your email.");
|
|
73
48
|
|
|
74
|
-
|
|
49
|
+
return Response.json({ ok: true });
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Send a new email
|
|
75
54
|
|
|
76
55
|
```ts
|
|
77
|
-
import
|
|
56
|
+
import primitive from "@primitivedotdev/sdk";
|
|
78
57
|
|
|
79
|
-
const
|
|
80
|
-
|
|
58
|
+
const client = primitive.client({
|
|
59
|
+
apiKey: process.env.PRIMITIVE_API_KEY!,
|
|
60
|
+
});
|
|
81
61
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
62
|
+
const result = await client.send({
|
|
63
|
+
from: "Support <support@example.com>",
|
|
64
|
+
to: "alice@example.com",
|
|
65
|
+
subject: "Hello",
|
|
66
|
+
bodyText: "Hi there",
|
|
67
|
+
// Use a unique key per logical send. Reusing a key returns the original
|
|
68
|
+
// response from the first send, which is how retries are deduplicated.
|
|
69
|
+
idempotencyKey: "customer-key-abc123",
|
|
70
|
+
wait: true,
|
|
71
|
+
waitTimeoutMs: 5000,
|
|
72
|
+
});
|
|
85
73
|
|
|
86
|
-
console.log(result.
|
|
74
|
+
console.log(result.id, result.status, result.queueId, result.deliveryStatus);
|
|
87
75
|
```
|
|
88
76
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
`send`, `reply`, and `forward` keep the HTTP request open until Primitive's
|
|
78
|
+
downstream SMTP transaction completes. In production, configure your runtime or
|
|
79
|
+
transport with a request timeout long enough for SMTP delivery, typically 30-60
|
|
80
|
+
seconds.
|
|
92
81
|
|
|
93
|
-
|
|
82
|
+
### About `wait` mode
|
|
94
83
|
|
|
95
|
-
|
|
96
|
-
|
|
84
|
+
When `wait: true`, the call returns the first downstream SMTP outcome (or
|
|
85
|
+
`waitTimeoutMs`, default 30000). Possible terminal `deliveryStatus` values:
|
|
97
86
|
|
|
98
|
-
|
|
99
|
-
|
|
87
|
+
- `delivered` accepted by the receiving MTA
|
|
88
|
+
- `bounced` rejected by the receiving MTA (the response is still 200 OK)
|
|
89
|
+
- `deferred` temporary failure, the receiving MTA may retry
|
|
90
|
+
- `wait_timeout` no outcome was observed in time. Treat as "outcome unknown."
|
|
91
|
+
The send may still complete after the response returns.
|
|
100
92
|
|
|
101
|
-
###
|
|
93
|
+
### Reply from a different address
|
|
102
94
|
|
|
103
|
-
|
|
95
|
+
`reply()` defaults the From address to the inbound recipient (the address that
|
|
96
|
+
received the email). When your verified outbound domain differs from your
|
|
97
|
+
inbound domain, pass `from` explicitly:
|
|
104
98
|
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
```ts
|
|
100
|
+
await client.reply(email, {
|
|
101
|
+
text: "Thanks for your email.",
|
|
102
|
+
from: "notifications@outbound.example.com",
|
|
103
|
+
});
|
|
109
104
|
```
|
|
110
105
|
|
|
111
|
-
|
|
106
|
+
### Forward an inbound email
|
|
112
107
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
```ts
|
|
109
|
+
await client.forward(email, {
|
|
110
|
+
to: "ops@example.com",
|
|
111
|
+
bodyText: "Can you take this one?",
|
|
112
|
+
});
|
|
113
|
+
```
|
|
117
114
|
|
|
118
|
-
|
|
115
|
+
## The normalized email object
|
|
119
116
|
|
|
120
|
-
|
|
117
|
+
`primitive.receive(...)` returns a normalized inbound email object that keeps the
|
|
118
|
+
common case clean:
|
|
121
119
|
|
|
122
120
|
```ts
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const event = buildEmailReceivedEvent({
|
|
126
|
-
email_id: "email-123",
|
|
127
|
-
endpoint_id: "endpoint-456",
|
|
128
|
-
message_id: "<msg@example.com>",
|
|
129
|
-
sender: "from@example.com",
|
|
130
|
-
recipient: "to@example.com",
|
|
131
|
-
subject: "Hello",
|
|
132
|
-
received_at: "2025-01-01T00:00:00Z",
|
|
133
|
-
smtp_helo: "mail.example.com",
|
|
134
|
-
smtp_mail_from: "from@example.com",
|
|
135
|
-
smtp_rcpt_to: ["to@example.com"],
|
|
136
|
-
raw_bytes: Buffer.from("hello"),
|
|
137
|
-
raw_sha256: "a".repeat(64),
|
|
138
|
-
raw_size_bytes: 5,
|
|
139
|
-
attempt_count: 1,
|
|
140
|
-
date_header: null,
|
|
141
|
-
download_url: "https://example.com/raw",
|
|
142
|
-
download_expires_at: "2025-01-02T00:00:00Z",
|
|
143
|
-
attachments_download_url: null,
|
|
144
|
-
auth: {
|
|
145
|
-
spf: "pass",
|
|
146
|
-
dmarc: "pass",
|
|
147
|
-
dmarcPolicy: "reject",
|
|
148
|
-
dmarcFromDomain: "example.com",
|
|
149
|
-
dmarcSpfAligned: true,
|
|
150
|
-
dmarcDkimAligned: true,
|
|
151
|
-
dmarcSpfStrict: false,
|
|
152
|
-
dmarcDkimStrict: false,
|
|
153
|
-
dkimSignatures: [],
|
|
154
|
-
},
|
|
155
|
-
analysis: {},
|
|
156
|
-
});
|
|
121
|
+
email.sender.address
|
|
122
|
+
email.sender.name
|
|
157
123
|
|
|
158
|
-
|
|
159
|
-
|
|
124
|
+
email.receivedBy
|
|
125
|
+
email.receivedByAll
|
|
160
126
|
|
|
161
|
-
|
|
127
|
+
email.replyTarget.address
|
|
128
|
+
email.replySubject
|
|
129
|
+
email.forwardSubject
|
|
162
130
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
- `RAW_EMAIL_INLINE_THRESHOLD`
|
|
166
|
-
- `signWebhookPayload(rawBody, secret, timestamp?)`
|
|
167
|
-
- `WEBHOOK_VERSION`
|
|
168
|
-
- contract input and payload helper types
|
|
131
|
+
email.subject
|
|
132
|
+
email.text
|
|
169
133
|
|
|
170
|
-
|
|
134
|
+
email.thread.messageId
|
|
135
|
+
email.thread.references
|
|
171
136
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
import {
|
|
176
|
-
bundleAttachments,
|
|
177
|
-
parseEmail,
|
|
178
|
-
parseEmailWithAttachments,
|
|
179
|
-
toParsedDataComplete,
|
|
180
|
-
} from "@primitivedotdev/sdk/parser";
|
|
181
|
-
|
|
182
|
-
const parsed = await parseEmailWithAttachments(emlBuffer);
|
|
183
|
-
const archive = await bundleAttachments(parsed.attachments);
|
|
184
|
-
const webhookParsed = toParsedDataComplete(parsed, null);
|
|
185
|
-
|
|
186
|
-
await parseEmail(emlBuffer.toString("utf8"));
|
|
137
|
+
email.raw
|
|
187
138
|
```
|
|
188
139
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
- `parseEmail(emlRaw)`
|
|
192
|
-
- `parseEmailWithAttachments(emlBuffer, options?)`
|
|
193
|
-
- `bundleAttachments(attachments)`
|
|
194
|
-
- `extractAttachmentMetadata(attachments)`
|
|
195
|
-
- `getAttachmentsStorageKey(emailId, sha256)`
|
|
196
|
-
- `toParsedDataComplete(parsed, attachmentsDownloadUrl)`
|
|
197
|
-
- `toWebhookAttachments(attachments)`
|
|
198
|
-
- `attachmentMetadataToWebhookAttachments(metadata)`
|
|
199
|
-
- `toCanonicalHeaders(parsed)`
|
|
200
|
-
- parser attachment and bundle types
|
|
140
|
+
Use `email.raw` when you need the original validated webhook event shape.
|
|
201
141
|
|
|
202
|
-
##
|
|
142
|
+
## Advanced usage
|
|
203
143
|
|
|
204
|
-
|
|
144
|
+
### Explicit receive form
|
|
205
145
|
|
|
206
|
-
|
|
146
|
+
If your framework does not expose a standard `Request`, use the lower-level
|
|
147
|
+
form:
|
|
207
148
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
149
|
+
```ts
|
|
150
|
+
const email = primitive.receive({
|
|
151
|
+
body: req.body,
|
|
152
|
+
headers: req.headers,
|
|
153
|
+
secret: process.env.PRIMITIVE_WEBHOOK_SECRET!,
|
|
154
|
+
});
|
|
155
|
+
```
|
|
211
156
|
|
|
212
|
-
|
|
157
|
+
### Generated API module
|
|
213
158
|
|
|
214
|
-
|
|
159
|
+
Use the API subpath when you want the full generated HTTP API surface:
|
|
215
160
|
|
|
216
161
|
```ts
|
|
217
|
-
import {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
} catch (error) {
|
|
222
|
-
if (error instanceof PrimitiveWebhookError) {
|
|
223
|
-
console.error(error.code, error.message);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
162
|
+
import { PrimitiveApiClient, getAccount } from "@primitivedotdev/sdk/api";
|
|
163
|
+
|
|
164
|
+
const api = new PrimitiveApiClient({ apiKey: process.env.PRIMITIVE_API_KEY });
|
|
165
|
+
const result = await getAccount({ client: api.client });
|
|
226
166
|
```
|
|
227
167
|
|
|
168
|
+
### Other advanced surfaces
|
|
169
|
+
|
|
170
|
+
- `@primitivedotdev/sdk/webhook`
|
|
171
|
+
- `@primitivedotdev/sdk/openapi`
|
|
172
|
+
- `@primitivedotdev/sdk/contract`
|
|
173
|
+
- `@primitivedotdev/sdk/parser`
|
|
174
|
+
- `primitive` CLI
|
|
175
|
+
|
|
228
176
|
## Development
|
|
229
177
|
|
|
230
178
|
From `sdks/sdk-node`:
|
|
@@ -240,51 +188,7 @@ pnpm build
|
|
|
240
188
|
Or from repo root `sdks/`:
|
|
241
189
|
|
|
242
190
|
```bash
|
|
243
|
-
make node-
|
|
191
|
+
make node-generate
|
|
244
192
|
make node-check
|
|
245
193
|
make node-build
|
|
246
194
|
```
|
|
247
|
-
|
|
248
|
-
## Package Layout
|
|
249
|
-
|
|
250
|
-
```text
|
|
251
|
-
sdk-node/
|
|
252
|
-
bin/
|
|
253
|
-
run.js
|
|
254
|
-
src/
|
|
255
|
-
api/
|
|
256
|
-
generated/
|
|
257
|
-
index.ts
|
|
258
|
-
contract/
|
|
259
|
-
contract.ts
|
|
260
|
-
index.ts
|
|
261
|
-
oclif/
|
|
262
|
-
api-command.ts
|
|
263
|
-
fish-completion.ts
|
|
264
|
-
index.ts
|
|
265
|
-
openapi/
|
|
266
|
-
index.ts
|
|
267
|
-
openapi.generated.ts
|
|
268
|
-
operations.generated.ts
|
|
269
|
-
parser/
|
|
270
|
-
attachment-bundler.ts
|
|
271
|
-
attachment-parser.ts
|
|
272
|
-
email-parser.ts
|
|
273
|
-
index.ts
|
|
274
|
-
mapping.ts
|
|
275
|
-
webhook/
|
|
276
|
-
auth.ts
|
|
277
|
-
encoding.ts
|
|
278
|
-
errors.ts
|
|
279
|
-
index.ts
|
|
280
|
-
parsing.ts
|
|
281
|
-
signing.ts
|
|
282
|
-
version.ts
|
|
283
|
-
generated/
|
|
284
|
-
email-received-event.validator.generated.ts
|
|
285
|
-
index.ts
|
|
286
|
-
schema.generated.ts
|
|
287
|
-
types.generated.ts
|
|
288
|
-
types.ts
|
|
289
|
-
validation.ts
|
|
290
|
-
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import addressparser from "nodemailer/lib/addressparser/index.js";
|
|
2
|
+
import isEmail from "validator/lib/isEmail.js";
|
|
3
|
+
|
|
4
|
+
//#region src/parser/address-parser.ts
|
|
5
|
+
const MAX_HEADER_LENGTH = 998;
|
|
6
|
+
const IS_EMAIL_OPTIONS = {
|
|
7
|
+
allow_ip_domain: true,
|
|
8
|
+
require_tld: true,
|
|
9
|
+
allow_display_name: false,
|
|
10
|
+
allow_utf8_local_part: true
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Strict parser for RFC 5322 From-style headers in security-bearing
|
|
14
|
+
* contexts (allowlist gates, permission grants).
|
|
15
|
+
*
|
|
16
|
+
* Rejects, without falling back to a "best guess":
|
|
17
|
+
* - empty / whitespace-only input
|
|
18
|
+
* - inputs longer than RFC 5322's 998-octet line limit
|
|
19
|
+
* - multi-address From (RFC 5322 allows it but it is vanishingly
|
|
20
|
+
* rare and ambiguous as an identity)
|
|
21
|
+
* - group syntax ("Friends: a@b.com, c@d.com;")
|
|
22
|
+
* - any address that fails validator's isEmail check with our chosen
|
|
23
|
+
* options. That covers per-part length limits, dot-atom rules,
|
|
24
|
+
* hostname-label rules, TLD requirement, and other RFC 5321/5322
|
|
25
|
+
* conformance checks.
|
|
26
|
+
*
|
|
27
|
+
* Returns ONLY the validated address, with no display name. Strict
|
|
28
|
+
* exists for gating decisions, where the address is the security-
|
|
29
|
+
* bearing field. Display names from addressparser are not trustworthy
|
|
30
|
+
* here: weird inputs like `Name <user@x.com> <attacker@y.com>` get
|
|
31
|
+
* parsed as a single entry whose `name` silently includes the second
|
|
32
|
+
* address. Surfacing that as a "parsed name" would invite downstream
|
|
33
|
+
* misuse, so we drop it. If you need the name, call
|
|
34
|
+
* {@link parseFromHeaderLoose} alongside (it returns null on failure
|
|
35
|
+
* anyway, so you can still gate on strict's Result).
|
|
36
|
+
*
|
|
37
|
+
* Returns a typed Result so callers can map the failure reason to
|
|
38
|
+
* stable error codes without inspecting message text.
|
|
39
|
+
*/
|
|
40
|
+
function parseFromHeader(header) {
|
|
41
|
+
if (header === null || header === void 0) return {
|
|
42
|
+
ok: false,
|
|
43
|
+
reason: "empty"
|
|
44
|
+
};
|
|
45
|
+
const trimmed = header.trim();
|
|
46
|
+
if (trimmed.length === 0) return {
|
|
47
|
+
ok: false,
|
|
48
|
+
reason: "empty"
|
|
49
|
+
};
|
|
50
|
+
if (Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) return {
|
|
51
|
+
ok: false,
|
|
52
|
+
reason: "too_long"
|
|
53
|
+
};
|
|
54
|
+
const parsed = addressparser(trimmed);
|
|
55
|
+
if (parsed.length > 1) return {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "multiple_addresses"
|
|
58
|
+
};
|
|
59
|
+
const entry = parsed[0];
|
|
60
|
+
if (entry === void 0) return {
|
|
61
|
+
ok: false,
|
|
62
|
+
reason: "invalid_address"
|
|
63
|
+
};
|
|
64
|
+
if ("group" in entry) return {
|
|
65
|
+
ok: false,
|
|
66
|
+
reason: "group_syntax"
|
|
67
|
+
};
|
|
68
|
+
const address = entry.address;
|
|
69
|
+
if (address === void 0 || !isEmail(address, IS_EMAIL_OPTIONS)) return {
|
|
70
|
+
ok: false,
|
|
71
|
+
reason: "invalid_address"
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
value: { address: address.toLowerCase() }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Lenient parser for display-only call sites (inbox card "from",
|
|
80
|
+
* log lines, debugging). Returns the first parseable address with its
|
|
81
|
+
* display name, or null.
|
|
82
|
+
*
|
|
83
|
+
* Differences from {@link parseFromHeader}:
|
|
84
|
+
* - Multi-address From returns the first address instead of rejecting
|
|
85
|
+
* - Group syntax is flattened into its member addresses
|
|
86
|
+
* - Returns null instead of a typed reason on failure
|
|
87
|
+
* - Includes the parsed display name in the result
|
|
88
|
+
*
|
|
89
|
+
* Do not use for permission gates or any decision that grants access.
|
|
90
|
+
* That is what {@link parseFromHeader} is for. Names returned here can
|
|
91
|
+
* include addressparser's recovery output (trailing tokens, garbage
|
|
92
|
+
* before the address); treat as opaque text for display.
|
|
93
|
+
*/
|
|
94
|
+
function parseFromHeaderLoose(header) {
|
|
95
|
+
if (header === null || header === void 0) return null;
|
|
96
|
+
const trimmed = header.trim();
|
|
97
|
+
if (trimmed.length === 0 || Buffer.byteLength(trimmed, "utf8") > MAX_HEADER_LENGTH) return null;
|
|
98
|
+
const parsed = addressparser(trimmed);
|
|
99
|
+
for (const entry of parsed) {
|
|
100
|
+
const candidates = "group" in entry && Array.isArray(entry.group) ? entry.group : [entry];
|
|
101
|
+
for (const candidate of candidates) {
|
|
102
|
+
const address = candidate.address;
|
|
103
|
+
if (address !== void 0 && isEmail(address, IS_EMAIL_OPTIONS)) return {
|
|
104
|
+
address: address.toLowerCase(),
|
|
105
|
+
name: candidate.name && candidate.name.length > 0 ? candidate.name : null
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
export { parseFromHeader, parseFromHeaderLoose };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This file is auto-generated by @hey-api/openapi-ts
|
|
2
|
-
export { addDomain, createEndpoint, createFilter, deleteDomain, deleteEmail, deleteEndpoint, deleteFilter, downloadAttachments, downloadRawEmail, getAccount, getEmail, getStorageStats, getWebhookSecret, listDeliveries, listDomains, listEmails, listEndpoints, listFilters, replayDelivery, replayEmailWebhooks, rotateWebhookSecret, testEndpoint, updateAccount, updateDomain, updateEndpoint, updateFilter, verifyDomain } from './sdk.gen.js';
|
|
2
|
+
export { addDomain, createEndpoint, createFilter, deleteDomain, deleteEmail, deleteEndpoint, deleteFilter, downloadAttachments, downloadRawEmail, getAccount, getEmail, getStorageStats, getWebhookSecret, listDeliveries, listDomains, listEmails, listEndpoints, listFilters, replayDelivery, replayEmailWebhooks, rotateWebhookSecret, sendEmail, testEndpoint, updateAccount, updateDomain, updateEndpoint, updateFilter, verifyDomain } from './sdk.gen.js';
|
|
@@ -345,3 +345,20 @@ export const replayDelivery = (options) => (options.client ?? client).post({
|
|
|
345
345
|
url: '/webhooks/deliveries/{id}/replay',
|
|
346
346
|
...options
|
|
347
347
|
});
|
|
348
|
+
/**
|
|
349
|
+
* Send outbound email
|
|
350
|
+
*
|
|
351
|
+
* Sends an outbound email through Primitive's outbound relay. By default
|
|
352
|
+
* the request returns once the relay accepts the message for delivery.
|
|
353
|
+
* Set `wait: true` to wait for the first downstream SMTP delivery outcome.
|
|
354
|
+
*
|
|
355
|
+
*/
|
|
356
|
+
export const sendEmail = (options) => (options.client ?? client).post({
|
|
357
|
+
security: [{ scheme: 'bearer', type: 'http' }],
|
|
358
|
+
url: '/send-mail',
|
|
359
|
+
...options,
|
|
360
|
+
headers: {
|
|
361
|
+
'Content-Type': 'application/json',
|
|
362
|
+
...options.headers
|
|
363
|
+
}
|
|
364
|
+
});
|