@develemail/sdk 0.4.0 → 0.4.1
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 +54 -0
- package/dist/index.cjs +46 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -163
- package/dist/index.d.ts +97 -163
- package/dist/index.js +46 -10
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -3,46 +3,31 @@ interface DevelEmailConfig {
|
|
|
3
3
|
baseUrl: string;
|
|
4
4
|
maxRetries?: number;
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* already base64). Mirrors `apps/api/src/domains/emails/types.ts`'s
|
|
9
|
-
* `AttachmentInput` field-for-field; kept as a distinct name from the
|
|
10
|
-
* ergonomic `AttachmentInput` below so the DTO contract test can assert the
|
|
11
|
-
* wire shape while `emails.send()`/`emails.batch()` callers get
|
|
12
|
-
* base64/Buffer ergonomics. Exported only for `apps/api`'s DTO contract
|
|
13
|
-
* test to import — not part of the ergonomic public surface callers build
|
|
14
|
-
* requests with.
|
|
15
|
-
*/
|
|
6
|
+
|
|
7
|
+
/** Wire-format attachment shape — what actually goes over HTTP. */
|
|
16
8
|
interface WireAttachmentInput {
|
|
17
9
|
filename: string;
|
|
18
10
|
content: string;
|
|
19
11
|
contentType: string;
|
|
20
12
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Ergonomic attachment input for `emails.send()` / `emails.batch()`.
|
|
23
|
-
* `content` accepts a `Uint8Array` (covers Node `Buffer`) — the SDK
|
|
24
|
-
* base64-encodes it before the request goes out — or an already-base64
|
|
25
|
-
* `string`, matching the wire format directly.
|
|
26
|
-
*
|
|
27
|
-
* Server caps: max 10 attachments per email, 5 MiB per file (decoded),
|
|
28
|
-
* 7 MiB combined decoded size per email.
|
|
29
|
-
*/
|
|
13
|
+
/** Ergonomic attachment input for `emails.send()` / `emails.batch()`. */
|
|
30
14
|
interface AttachmentInput {
|
|
31
15
|
filename: string;
|
|
32
16
|
content: string | Uint8Array;
|
|
33
17
|
contentType: string;
|
|
34
18
|
}
|
|
35
19
|
/**
|
|
36
|
-
* Deliberately different from `
|
|
37
|
-
*
|
|
38
|
-
* shape
|
|
39
|
-
* `z.infer`'d type is the post-parse wire shape (`content: string`,
|
|
40
|
-
* already base64). Every other field matches exactly — see
|
|
41
|
-
* `apps/api/src/sdk-dto-contract.test.ts`'s `Omit<..., 'attachments'>`
|
|
42
|
-
* assertion. See `apps/api/src/sdk-dto-contract-discovery.test.ts`'s
|
|
43
|
-
* registry for the enforcement.
|
|
20
|
+
* Deliberately different from the API's `SendEmailInput` in `attachments`
|
|
21
|
+
* only: this is the ergonomic pre-request shape while the API type is the
|
|
22
|
+
* post-parse wire shape. Every other field matches exactly.
|
|
44
23
|
*/
|
|
45
24
|
interface SendEmailInput {
|
|
25
|
+
/**
|
|
26
|
+
* Sender. Either a bare address (`noreply@example.com`) or a display-name
|
|
27
|
+
* mailbox (`Acme <noreply@example.com>`). Names containing commas or
|
|
28
|
+
* specials must be quoted (`"Acme, Inc." <noreply@example.com>`); non-ASCII
|
|
29
|
+
* names are encoded for you. The address domain must be verified.
|
|
30
|
+
*/
|
|
46
31
|
from: string;
|
|
47
32
|
to: string | string[];
|
|
48
33
|
subject?: string;
|
|
@@ -54,17 +39,15 @@ interface SendEmailInput {
|
|
|
54
39
|
variables?: Record<string, string>;
|
|
55
40
|
send_at?: string;
|
|
56
41
|
attachments?: AttachmentInput[];
|
|
42
|
+
/** RFC 5322 Message-ID (angle brackets included, e.g. `<id@host>`). */
|
|
43
|
+
inReplyTo?: string;
|
|
44
|
+
/** Same Message-ID format as `inReplyTo`, oldest first. */
|
|
45
|
+
references?: string[];
|
|
57
46
|
}
|
|
58
47
|
interface SendOptions {
|
|
59
48
|
idempotencyKey?: string;
|
|
60
49
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Mirrors `apps/api/src/domains/emails/types.ts`'s server-side copy of this
|
|
63
|
-
* shape. Not imported from there: this package is published to npm for
|
|
64
|
-
* external consumers and can't depend on the private api app. Keep both
|
|
65
|
-
* copies in sync by hand when the wire shape changes; same for
|
|
66
|
-
* `BatchEmailResultItem` / `BatchEmailResponse` below.
|
|
67
|
-
*/
|
|
50
|
+
/** Mirrors `apps/api/src/domains/emails/types.ts`'s server-side copy. */
|
|
68
51
|
interface EmailResponse {
|
|
69
52
|
id: string;
|
|
70
53
|
status: string;
|
|
@@ -76,6 +59,7 @@ interface BatchEmailResultItem {
|
|
|
76
59
|
status?: string;
|
|
77
60
|
duplicate?: boolean;
|
|
78
61
|
error?: string;
|
|
62
|
+
code?: string;
|
|
79
63
|
}
|
|
80
64
|
interface BatchEmailResponse {
|
|
81
65
|
results: BatchEmailResultItem[];
|
|
@@ -83,6 +67,20 @@ interface BatchEmailResponse {
|
|
|
83
67
|
errors: number;
|
|
84
68
|
limitReached?: boolean;
|
|
85
69
|
}
|
|
70
|
+
/** Every status an email can be in. */
|
|
71
|
+
declare const EMAIL_STATUSES: readonly ["queued", "rendering", "submitted_to_postfix", "deferred", "sent", "delivered", "bounced", "rejected", "failed", "suppressed", "cancelled", "scheduled", "quarantined", "sandboxed"];
|
|
72
|
+
type EmailStatus = (typeof EMAIL_STATUSES)[number];
|
|
73
|
+
/** Every event type the server currently emits. */
|
|
74
|
+
declare const EMAIL_EVENT_TYPES: readonly ["queued", "rendering_started", "rendering_completed", "submitted_to_postfix", "deferred", "sent", "delivered", "bounced", "rejected", "failed", "suppressed", "cancelled", "retry_scheduled", "open", "click", "quarantined"];
|
|
75
|
+
type EmailEventType = (typeof EMAIL_EVENT_TYPES)[number];
|
|
76
|
+
/** `metadata` shape for Postfix-derived lifecycle events. */
|
|
77
|
+
interface DeliveryEventData {
|
|
78
|
+
queueId: string;
|
|
79
|
+
relay: string;
|
|
80
|
+
response: string;
|
|
81
|
+
timestamp: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
86
84
|
interface Domain {
|
|
87
85
|
id: string;
|
|
88
86
|
organizationId: string;
|
|
@@ -100,6 +98,11 @@ interface Domain {
|
|
|
100
98
|
createdAt: string;
|
|
101
99
|
updatedAt: string;
|
|
102
100
|
}
|
|
101
|
+
interface DomainCreateInput {
|
|
102
|
+
name: string;
|
|
103
|
+
dkimSelector?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
103
106
|
interface Template {
|
|
104
107
|
id: string;
|
|
105
108
|
organizationId: string;
|
|
@@ -111,10 +114,6 @@ interface Template {
|
|
|
111
114
|
createdAt: string;
|
|
112
115
|
updatedAt: string;
|
|
113
116
|
}
|
|
114
|
-
interface DomainCreateInput {
|
|
115
|
-
name: string;
|
|
116
|
-
dkimSelector?: string;
|
|
117
|
-
}
|
|
118
117
|
interface TemplateCreateInput {
|
|
119
118
|
name: string;
|
|
120
119
|
subject: string;
|
|
@@ -129,39 +128,8 @@ interface TemplateUpdateInput {
|
|
|
129
128
|
text?: string;
|
|
130
129
|
variables?: string[];
|
|
131
130
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
* gateway accepted the message with a 2xx response — it does not mean the
|
|
135
|
-
* message reached the inbox; it can still be filtered or quarantined
|
|
136
|
-
* afterward.
|
|
137
|
-
*/
|
|
138
|
-
declare const EMAIL_STATUSES: readonly ["queued", "rendering", "submitted_to_postfix", "deferred", "sent", "delivered", "bounced", "rejected", "failed", "suppressed", "cancelled", "scheduled", "quarantined"];
|
|
139
|
-
type EmailStatus = (typeof EMAIL_STATUSES)[number];
|
|
140
|
-
/**
|
|
141
|
-
* Every event type the server currently emits, including as a webhook's
|
|
142
|
-
* `event` field. `WebhookEvent.event` stays typed as `string` (not this
|
|
143
|
-
* union) because the server may add new event types over time — treat an
|
|
144
|
-
* unrecognized value as informational, not an error. See
|
|
145
|
-
* `docs/webhooks.md`: a project's webhook receives every event type below
|
|
146
|
-
* automatically, with no per-type opt-in.
|
|
147
|
-
*/
|
|
148
|
-
declare const EMAIL_EVENT_TYPES: readonly ["queued", "rendering_started", "rendering_completed", "submitted_to_postfix", "deferred", "sent", "delivered", "bounced", "rejected", "failed", "suppressed", "cancelled", "retry_scheduled", "open", "click", "quarantined"];
|
|
149
|
-
type EmailEventType = (typeof EMAIL_EVENT_TYPES)[number];
|
|
150
|
-
/**
|
|
151
|
-
* `metadata` shape for `delivered` / `deferred` / `bounced` webhook events
|
|
152
|
-
* and lifecycle-timeline entries — written by the Postfix log follower.
|
|
153
|
-
*/
|
|
154
|
-
interface DeliveryEventData {
|
|
155
|
-
queueId: string;
|
|
156
|
-
relay: string;
|
|
157
|
-
response: string;
|
|
158
|
-
timestamp: string;
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Mirrors `apps/api/src/domains/webhooks/types.ts`'s `WebhookPayload`
|
|
162
|
-
* (different name, same shape) — see that file for why this isn't a shared
|
|
163
|
-
* import.
|
|
164
|
-
*/
|
|
131
|
+
|
|
132
|
+
/** Mirrors `apps/api/src/domains/webhooks/types.ts`'s `WebhookPayload`. */
|
|
165
133
|
interface WebhookEvent {
|
|
166
134
|
event: string;
|
|
167
135
|
emailId: string | null;
|
|
@@ -170,6 +138,7 @@ interface WebhookEvent {
|
|
|
170
138
|
timestamp: string;
|
|
171
139
|
metadata: Record<string, unknown>;
|
|
172
140
|
}
|
|
141
|
+
|
|
173
142
|
type CampaignStatus = 'draft' | 'scheduled' | 'sending' | 'paused' | 'sent' | 'cancelled';
|
|
174
143
|
interface Campaign {
|
|
175
144
|
id: string;
|
|
@@ -194,20 +163,13 @@ interface Campaign {
|
|
|
194
163
|
opened?: number;
|
|
195
164
|
clicked?: number;
|
|
196
165
|
bounced?: number;
|
|
166
|
+
configurationBounced?: number;
|
|
197
167
|
unsubscribed?: number;
|
|
198
168
|
} | null;
|
|
199
169
|
createdAt: string;
|
|
200
170
|
updatedAt: string;
|
|
201
171
|
}
|
|
202
|
-
/**
|
|
203
|
-
* Deliberately different from `apps/api/src/domains/campaigns/types.ts`'s
|
|
204
|
-
* `CampaignListQuery` in `limit`/`offset` optionality, not a bug: this type
|
|
205
|
-
* describes what a caller may omit before the request is sent, while the
|
|
206
|
-
* api's `z.infer`'d type describes the post-parse shape after `.default()`
|
|
207
|
-
* has filled those fields in. Both are correct for what they represent — see
|
|
208
|
-
* `apps/api/src/sdk-dto-contract-discovery.test.ts`'s registry for the
|
|
209
|
-
* enforcement.
|
|
210
|
-
*/
|
|
172
|
+
/** The SDK accepts optional pagination before the API applies defaults. */
|
|
211
173
|
interface CampaignListQuery {
|
|
212
174
|
projectId: string;
|
|
213
175
|
status?: CampaignStatus;
|
|
@@ -241,11 +203,7 @@ interface CampaignUpdateInput {
|
|
|
241
203
|
rawText?: string;
|
|
242
204
|
scheduledFor?: string;
|
|
243
205
|
}
|
|
244
|
-
/**
|
|
245
|
-
* Mirrors `apps/api/src/domains/campaigns/types.ts`'s server-side copy of
|
|
246
|
-
* this shape (and `CampaignProviderStats` below) — see that file for why
|
|
247
|
-
* this isn't a shared import.
|
|
248
|
-
*/
|
|
206
|
+
/** Mirrors `apps/api/src/domains/campaigns/types.ts`'s server-side copy. */
|
|
249
207
|
interface CampaignStats {
|
|
250
208
|
campaignId: string;
|
|
251
209
|
totalRecipients: number;
|
|
@@ -253,7 +211,9 @@ interface CampaignStats {
|
|
|
253
211
|
deliveryUnknown: number;
|
|
254
212
|
opened: number;
|
|
255
213
|
clicked: number;
|
|
214
|
+
/** Reputation bounces only; configuration bounces are in `configurationBounced`. */
|
|
256
215
|
bounced: number;
|
|
216
|
+
configurationBounced: number;
|
|
257
217
|
unsubscribed: number;
|
|
258
218
|
openRate: number;
|
|
259
219
|
clickRate: number;
|
|
@@ -269,44 +229,7 @@ interface CampaignProviderStats {
|
|
|
269
229
|
clicks: number;
|
|
270
230
|
total: number;
|
|
271
231
|
}
|
|
272
|
-
|
|
273
|
-
id: string;
|
|
274
|
-
projectId: string;
|
|
275
|
-
name: string;
|
|
276
|
-
description: string | null;
|
|
277
|
-
createdAt: string;
|
|
278
|
-
updatedAt: string;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Deliberately different from `apps/api/src/domains/lists/types.ts`'s
|
|
282
|
-
* `ListWithCount` in `createdAt`/`updatedAt` (inherited from `List` above),
|
|
283
|
-
* not a bug: this describes what a client receives after the wire
|
|
284
|
-
* round-trip (`string`), while the api's type is the server-side shape held
|
|
285
|
-
* before JSON serialization (`Date`, via drizzle's `$inferSelect`). See
|
|
286
|
-
* `apps/api/src/sdk-dto-contract-discovery.test.ts`'s registry for the
|
|
287
|
-
* enforcement.
|
|
288
|
-
*/
|
|
289
|
-
interface ListWithCount extends List {
|
|
290
|
-
memberCount: number;
|
|
291
|
-
}
|
|
292
|
-
interface ListCreateInput {
|
|
293
|
-
projectId: string;
|
|
294
|
-
name: string;
|
|
295
|
-
description?: string;
|
|
296
|
-
}
|
|
297
|
-
interface ListUpdateInput {
|
|
298
|
-
name?: string;
|
|
299
|
-
description?: string;
|
|
300
|
-
}
|
|
301
|
-
interface ListMembersQuery {
|
|
302
|
-
search?: string;
|
|
303
|
-
limit?: number;
|
|
304
|
-
offset?: number;
|
|
305
|
-
}
|
|
306
|
-
interface ListMembersResponse {
|
|
307
|
-
data: Contact[];
|
|
308
|
-
total: number;
|
|
309
|
-
}
|
|
232
|
+
|
|
310
233
|
interface Contact {
|
|
311
234
|
id: string;
|
|
312
235
|
projectId: string;
|
|
@@ -319,15 +242,7 @@ interface Contact {
|
|
|
319
242
|
createdAt: string;
|
|
320
243
|
updatedAt: string;
|
|
321
244
|
}
|
|
322
|
-
/**
|
|
323
|
-
* Deliberately different from `apps/api/src/domains/contacts/types.ts`'s
|
|
324
|
-
* `ContactListQuery` in `tags`/`tagMode`, not a bug: the api type is
|
|
325
|
-
* `z.infer<typeof ContactListQuerySchema>` — `tags` is post-transform
|
|
326
|
-
* `string[]` and `tagMode` is post-`.default()` required — while this is the
|
|
327
|
-
* pre-request shape callers build, where both stay optional. See
|
|
328
|
-
* `apps/api/src/sdk-dto-contract-discovery.test.ts`'s registry for the
|
|
329
|
-
* enforcement.
|
|
330
|
-
*/
|
|
245
|
+
/** The SDK preserves optional filters before API parsing/defaulting. */
|
|
331
246
|
interface ContactListQuery {
|
|
332
247
|
projectId: string;
|
|
333
248
|
search?: string;
|
|
@@ -366,36 +281,49 @@ interface ContactBulkResponse {
|
|
|
366
281
|
updated: number;
|
|
367
282
|
skipped: number;
|
|
368
283
|
}
|
|
369
|
-
/**
|
|
370
|
-
* Deliberately different from `apps/api/src/domains/contacts/types.ts`'s
|
|
371
|
-
* `ContactListItem` in `addedAt`, not a bug: this describes what a client
|
|
372
|
-
* receives after the wire round-trip (`string`), while the api's type is
|
|
373
|
-
* the server-side shape held before JSON serialization (`Date`). See
|
|
374
|
-
* `apps/api/src/sdk-dto-contract-discovery.test.ts`'s registry for the
|
|
375
|
-
* enforcement.
|
|
376
|
-
*/
|
|
284
|
+
/** `addedAt` is serialized to string for SDK consumers. */
|
|
377
285
|
interface ContactListItem {
|
|
378
286
|
id: string;
|
|
379
287
|
name: string;
|
|
380
288
|
memberCount: number;
|
|
381
289
|
addedAt: string;
|
|
382
290
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
291
|
+
|
|
292
|
+
interface List {
|
|
293
|
+
id: string;
|
|
294
|
+
projectId: string;
|
|
295
|
+
name: string;
|
|
296
|
+
description: string | null;
|
|
297
|
+
createdAt: string;
|
|
298
|
+
updatedAt: string;
|
|
299
|
+
}
|
|
300
|
+
/** API dates are serialized to strings for SDK consumers. */
|
|
301
|
+
interface ListWithCount extends List {
|
|
302
|
+
memberCount: number;
|
|
303
|
+
}
|
|
304
|
+
interface ListCreateInput {
|
|
305
|
+
projectId: string;
|
|
306
|
+
name: string;
|
|
307
|
+
description?: string;
|
|
308
|
+
}
|
|
309
|
+
interface ListUpdateInput {
|
|
310
|
+
name?: string;
|
|
311
|
+
description?: string;
|
|
312
|
+
}
|
|
313
|
+
interface ListMembersQuery {
|
|
314
|
+
search?: string;
|
|
315
|
+
limit?: number;
|
|
316
|
+
offset?: number;
|
|
317
|
+
}
|
|
318
|
+
interface ListMembersResponse {
|
|
319
|
+
data: Contact[];
|
|
320
|
+
total: number;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Mirrors `apps/api/src/domains/sms/repo.types.ts`'s status values. */
|
|
389
324
|
declare const SMS_STATUSES: readonly ["queued", "sending", "sent", "delivered", "deferred", "failed", "suppressed"];
|
|
390
325
|
type SmsStatus = (typeof SMS_STATUSES)[number];
|
|
391
|
-
/**
|
|
392
|
-
* Mirrors `apps/api/src/domains/sms/types.ts`'s `z.infer`'d `SendSmsInput`
|
|
393
|
-
* field-for-field — unlike `SendEmailInput`, there's no ergonomic wire
|
|
394
|
-
* conversion needed here, so this is asserted equal (not
|
|
395
|
-
* `DELIBERATELY_DIFFERENT`) in `apps/api/src/sdk-dto-contract.test.ts`.
|
|
396
|
-
* Provide exactly one of `body` or `templateName` — the server 400s
|
|
397
|
-
* otherwise; that XOR is a runtime `.refine()`, not expressible in the type.
|
|
398
|
-
*/
|
|
326
|
+
/** Mirrors `apps/api/src/domains/sms/types.ts`'s `SendSmsInput`. */
|
|
399
327
|
interface SendSmsInput {
|
|
400
328
|
to: string;
|
|
401
329
|
from?: string;
|
|
@@ -405,13 +333,6 @@ interface SendSmsInput {
|
|
|
405
333
|
tags?: string[];
|
|
406
334
|
metadata?: Record<string, unknown>;
|
|
407
335
|
}
|
|
408
|
-
/**
|
|
409
|
-
* Mirrors `apps/api/src/domains/sms/types.ts`'s server-side copy of this
|
|
410
|
-
* shape field-for-field. Not imported from there: this package is published
|
|
411
|
-
* to npm for external consumers and can't depend on the private api app.
|
|
412
|
-
* Keep both copies in sync by hand when the wire shape changes; same for
|
|
413
|
-
* `SmsListResponse` below.
|
|
414
|
-
*/
|
|
415
336
|
interface SmsMessageResponse {
|
|
416
337
|
id: string;
|
|
417
338
|
to: string;
|
|
@@ -432,6 +353,18 @@ interface SmsListQuery {
|
|
|
432
353
|
limit?: number;
|
|
433
354
|
cursor?: string;
|
|
434
355
|
}
|
|
356
|
+
interface BatchSmsResultItem {
|
|
357
|
+
index: number;
|
|
358
|
+
id?: string;
|
|
359
|
+
status?: string;
|
|
360
|
+
error?: string;
|
|
361
|
+
code?: string;
|
|
362
|
+
}
|
|
363
|
+
interface BatchSmsResponse {
|
|
364
|
+
results: BatchSmsResultItem[];
|
|
365
|
+
created: number;
|
|
366
|
+
errors: number;
|
|
367
|
+
}
|
|
435
368
|
|
|
436
369
|
declare class HttpClient {
|
|
437
370
|
private readonly baseUrl;
|
|
@@ -529,7 +462,8 @@ declare class Lists {
|
|
|
529
462
|
declare class Sms {
|
|
530
463
|
private readonly http;
|
|
531
464
|
constructor(http: HttpClient);
|
|
532
|
-
send(input: SendSmsInput): Promise<SmsMessageResponse>;
|
|
465
|
+
send(input: SendSmsInput, options?: SendOptions): Promise<SmsMessageResponse>;
|
|
466
|
+
sendBatch(messages: SendSmsInput[], options?: SendOptions): Promise<BatchSmsResponse>;
|
|
533
467
|
get(id: string): Promise<SmsMessageResponse>;
|
|
534
468
|
list(query?: SmsListQuery): Promise<SmsListResponse>;
|
|
535
469
|
}
|
|
@@ -554,4 +488,4 @@ declare class DevelEmail {
|
|
|
554
488
|
constructor(config: DevelEmailConfig);
|
|
555
489
|
}
|
|
556
490
|
|
|
557
|
-
export { type AttachmentInput, type BatchEmailResponse, type BatchEmailResultItem, type Campaign, type CampaignCreateInput, type CampaignListQuery, type CampaignProviderStats, type CampaignStats, type CampaignStatus, type CampaignUpdateInput, type Contact, type ContactBulkContactInput, type ContactBulkInput, type ContactBulkResponse, type ContactCreateInput, type ContactListItem, type ContactListQuery, type ContactListResponse, type ContactUpdateInput, type DeliveryEventData, DevelEmail, type DevelEmailConfig, DevelEmailError, type Domain, type DomainCreateInput, EMAIL_EVENT_TYPES, EMAIL_STATUSES, type EmailEventType, type EmailResponse, type EmailStatus, type List, type ListCreateInput, type ListMembersQuery, type ListMembersResponse, type ListUpdateInput, type ListWithCount, SMS_STATUSES, type SendEmailInput, type SendOptions, type SendSmsInput, type SmsListQuery, type SmsListResponse, type SmsMessageResponse, type SmsStatus, type Template, type TemplateCreateInput, type TemplateUpdateInput, type WebhookEvent, type WireAttachmentInput, verifyWebhookSignature };
|
|
491
|
+
export { type AttachmentInput, type BatchEmailResponse, type BatchEmailResultItem, type BatchSmsResponse, type BatchSmsResultItem, type Campaign, type CampaignCreateInput, type CampaignListQuery, type CampaignProviderStats, type CampaignStats, type CampaignStatus, type CampaignUpdateInput, type Contact, type ContactBulkContactInput, type ContactBulkInput, type ContactBulkResponse, type ContactCreateInput, type ContactListItem, type ContactListQuery, type ContactListResponse, type ContactUpdateInput, type DeliveryEventData, DevelEmail, type DevelEmailConfig, DevelEmailError, type Domain, type DomainCreateInput, EMAIL_EVENT_TYPES, EMAIL_STATUSES, type EmailEventType, type EmailResponse, type EmailStatus, type List, type ListCreateInput, type ListMembersQuery, type ListMembersResponse, type ListUpdateInput, type ListWithCount, SMS_STATUSES, type SendEmailInput, type SendOptions, type SendSmsInput, type SmsListQuery, type SmsListResponse, type SmsMessageResponse, type SmsStatus, type Template, type TemplateCreateInput, type TemplateUpdateInput, type WebhookEvent, type WireAttachmentInput, verifyWebhookSignature };
|
package/dist/index.js
CHANGED
|
@@ -16,19 +16,31 @@ var DevelEmailError = class extends Error {
|
|
|
16
16
|
var DEFAULT_MAX_RETRIES = 3;
|
|
17
17
|
var BASE_DELAY_MS = 500;
|
|
18
18
|
var MAX_DELAY_MS = 3e4;
|
|
19
|
+
var MAX_RETRY_AFTER_MS = 1e4;
|
|
20
|
+
var CONFLICT_RETRY_DELAY_MS = 1e3;
|
|
19
21
|
function sleep(ms) {
|
|
20
22
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
23
|
}
|
|
22
24
|
function isRetryable(status) {
|
|
23
25
|
return status >= 500 || status === 429;
|
|
24
26
|
}
|
|
27
|
+
function isIdempotencyConflict(status, code) {
|
|
28
|
+
return status === 409 && code === "idempotency_conflict";
|
|
29
|
+
}
|
|
30
|
+
function parseRetryAfterMs(header) {
|
|
31
|
+
if (!header) return null;
|
|
32
|
+
const seconds = Number(header);
|
|
33
|
+
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
34
|
+
return Math.min(seconds * 1e3, MAX_RETRY_AFTER_MS);
|
|
35
|
+
}
|
|
25
36
|
function parseErrorBody(body) {
|
|
26
37
|
if (typeof body === "object" && body !== null) {
|
|
27
38
|
const b = body;
|
|
28
39
|
const { message, error, statusCode, ...rest } = b;
|
|
40
|
+
const code = typeof b.code === "string" ? b.code : typeof error === "string" ? error : void 0;
|
|
29
41
|
return {
|
|
30
42
|
message: typeof message === "string" ? message : "Unknown error",
|
|
31
|
-
code
|
|
43
|
+
code,
|
|
32
44
|
details: Object.keys(rest).length > 0 ? rest : void 0
|
|
33
45
|
};
|
|
34
46
|
}
|
|
@@ -71,6 +83,7 @@ var HttpClient = class {
|
|
|
71
83
|
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
72
84
|
};
|
|
73
85
|
let lastError;
|
|
86
|
+
let retriedConflict = false;
|
|
74
87
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
75
88
|
let res;
|
|
76
89
|
try {
|
|
@@ -93,9 +106,11 @@ var HttpClient = class {
|
|
|
93
106
|
}
|
|
94
107
|
const errorBody = await res.json().catch(() => ({}));
|
|
95
108
|
const parsed = parseErrorBody(errorBody);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
109
|
+
const conflictRetry = isIdempotencyConflict(res.status, parsed.code) && !retriedConflict;
|
|
110
|
+
if ((isRetryable(res.status) || conflictRetry) && attempt < this.maxRetries) {
|
|
111
|
+
if (conflictRetry) retriedConflict = true;
|
|
112
|
+
const retryAfterMs = parseRetryAfterMs(res.headers.get("Retry-After"));
|
|
113
|
+
const delay = retryAfterMs ?? (conflictRetry ? CONFLICT_RETRY_DELAY_MS : this.backoff(attempt));
|
|
99
114
|
await sleep(delay);
|
|
100
115
|
lastError = new DevelEmailError(
|
|
101
116
|
res.status,
|
|
@@ -128,7 +143,13 @@ var HttpClient = class {
|
|
|
128
143
|
|
|
129
144
|
// ../core/src/reserved-domains.ts
|
|
130
145
|
var RESERVED_DOMAINS = ["example.com", "example.net", "example.org"];
|
|
131
|
-
var RESERVED_TLDS = [
|
|
146
|
+
var RESERVED_TLDS = [
|
|
147
|
+
"test",
|
|
148
|
+
"example",
|
|
149
|
+
"invalid",
|
|
150
|
+
"localhost",
|
|
151
|
+
"local"
|
|
152
|
+
];
|
|
132
153
|
function isReservedDomain(domain) {
|
|
133
154
|
const normalized = domain.trim().toLowerCase().replace(/\.$/, "");
|
|
134
155
|
if (!normalized) return false;
|
|
@@ -194,7 +215,8 @@ var Emails = class {
|
|
|
194
215
|
if (reserved) {
|
|
195
216
|
rejections.set(index, {
|
|
196
217
|
index,
|
|
197
|
-
error: reservedRecipientError(reserved).message
|
|
218
|
+
error: reservedRecipientError(reserved).message,
|
|
219
|
+
code: "reserved_recipient_domain"
|
|
198
220
|
});
|
|
199
221
|
}
|
|
200
222
|
});
|
|
@@ -484,8 +506,19 @@ var Sms = class {
|
|
|
484
506
|
this.http = http;
|
|
485
507
|
}
|
|
486
508
|
http;
|
|
487
|
-
async send(input) {
|
|
488
|
-
|
|
509
|
+
async send(input, options) {
|
|
510
|
+
const key = options?.idempotencyKey ?? crypto.randomUUID();
|
|
511
|
+
return this.http.post("/v1/sms", input, {
|
|
512
|
+
"Idempotency-Key": key
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
async sendBatch(messages, options) {
|
|
516
|
+
const key = options?.idempotencyKey ?? crypto.randomUUID();
|
|
517
|
+
return this.http.post(
|
|
518
|
+
"/v1/sms/batch",
|
|
519
|
+
{ messages },
|
|
520
|
+
{ "Idempotency-Key": key }
|
|
521
|
+
);
|
|
489
522
|
}
|
|
490
523
|
async get(id) {
|
|
491
524
|
return this.http.get(
|
|
@@ -523,7 +556,7 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
523
556
|
return diff === 0;
|
|
524
557
|
}
|
|
525
558
|
|
|
526
|
-
// src/types.ts
|
|
559
|
+
// src/types/emails.ts
|
|
527
560
|
var EMAIL_STATUSES = [
|
|
528
561
|
"queued",
|
|
529
562
|
"rendering",
|
|
@@ -537,7 +570,8 @@ var EMAIL_STATUSES = [
|
|
|
537
570
|
"suppressed",
|
|
538
571
|
"cancelled",
|
|
539
572
|
"scheduled",
|
|
540
|
-
"quarantined"
|
|
573
|
+
"quarantined",
|
|
574
|
+
"sandboxed"
|
|
541
575
|
];
|
|
542
576
|
var EMAIL_EVENT_TYPES = [
|
|
543
577
|
"queued",
|
|
@@ -557,6 +591,8 @@ var EMAIL_EVENT_TYPES = [
|
|
|
557
591
|
"click",
|
|
558
592
|
"quarantined"
|
|
559
593
|
];
|
|
594
|
+
|
|
595
|
+
// src/types/sms.ts
|
|
560
596
|
var SMS_STATUSES = [
|
|
561
597
|
"queued",
|
|
562
598
|
"sending",
|