shipmail 0.1.20 → 0.1.22
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 +358 -106
- package/dist/index.cjs +188 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +337 -5
- package/dist/index.d.ts +337 -5
- package/dist/index.js +188 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,219 +1,471 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# Shipmail TypeScript SDK
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/shipmail)
|
|
4
|
+
[](https://www.npmjs.com/package/shipmail)
|
|
5
|
+
[](https://bundlephobia.com/package/shipmail)
|
|
6
|
+
[](https://www.npmjs.com/package/shipmail)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
Official TypeScript SDK for the [Shipmail](https://shipmail.to) API. Zero runtime dependencies. Native `fetch`. Full TypeScript types. ESM and CommonJS.
|
|
10
|
+
|
|
11
|
+
**Runtimes**: Node.js 18+, Bun, Deno. Webhook verification uses `node:crypto` (enable `nodejs_compat` on Cloudflare Workers if you verify webhooks there).
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- [Install](#install)
|
|
16
|
+
- [Quick start](#quick-start)
|
|
17
|
+
- [Configuration](#configuration)
|
|
18
|
+
- [Domains](#domains)
|
|
19
|
+
- [Mailboxes](#mailboxes)
|
|
20
|
+
- [Messages](#messages)
|
|
21
|
+
- [Threads](#threads)
|
|
22
|
+
- [Webhooks](#webhooks)
|
|
23
|
+
- [Suppressions](#suppressions)
|
|
24
|
+
- [Status](#status)
|
|
25
|
+
- [Pagination](#pagination)
|
|
26
|
+
- [Webhook verification](#webhook-verification)
|
|
27
|
+
- [Per-request options](#per-request-options)
|
|
28
|
+
- [Idempotency](#idempotency)
|
|
29
|
+
- [Cancellation](#cancellation)
|
|
30
|
+
- [Custom fetch and proxies](#custom-fetch-and-proxies)
|
|
31
|
+
- [Errors](#errors)
|
|
32
|
+
- [Retries](#retries)
|
|
33
|
+
- [Bundling](#bundling)
|
|
34
|
+
- [Testing](#testing)
|
|
35
|
+
- [License](#license)
|
|
36
|
+
- [Links](#links)
|
|
37
|
+
|
|
38
|
+
## Install
|
|
6
39
|
|
|
7
40
|
```bash
|
|
41
|
+
bun add shipmail
|
|
42
|
+
# or
|
|
8
43
|
npm install shipmail
|
|
9
44
|
# or
|
|
10
|
-
|
|
45
|
+
pnpm add shipmail
|
|
11
46
|
```
|
|
12
47
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Quick Start
|
|
48
|
+
## Quick start
|
|
16
49
|
|
|
17
|
-
```
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
const client = new ShipMail("sm_live_...");
|
|
50
|
+
```ts
|
|
51
|
+
import { ShipMailClient } from "shipmail";
|
|
21
52
|
|
|
22
|
-
|
|
23
|
-
const domain = await client.domains.create({ name: "example.com" });
|
|
53
|
+
const shipmail = new ShipMailClient({ apiKey: process.env.SHIPMAIL_API_KEY! });
|
|
24
54
|
|
|
25
|
-
|
|
26
|
-
const message = await client.messages.send({
|
|
55
|
+
const message = await shipmail.messages.send({
|
|
27
56
|
mailbox_id: "mbx_...",
|
|
28
|
-
to: [{ address: "user@example.com" }],
|
|
57
|
+
to: [{ address: "user@example.com", name: "User" }],
|
|
29
58
|
subject: "Hello",
|
|
30
|
-
|
|
59
|
+
text: "Hi there",
|
|
60
|
+
html: "<p>Hi there</p>",
|
|
31
61
|
});
|
|
32
62
|
```
|
|
33
63
|
|
|
34
|
-
|
|
64
|
+
The SDK does not auto-read environment variables. Pass the key explicitly.
|
|
35
65
|
|
|
36
|
-
|
|
37
|
-
import { ShipMailClient } from "shipmail";
|
|
66
|
+
You can also pass a key string directly:
|
|
38
67
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
baseUrl: "https://shipmail.to/api/v1", // default
|
|
42
|
-
maxRetries: 2, // default, retries on 5xx and 429
|
|
43
|
-
timeout: 30_000, // default, in milliseconds
|
|
44
|
-
});
|
|
68
|
+
```ts
|
|
69
|
+
const shipmail = new ShipMailClient("sm_live_...");
|
|
45
70
|
```
|
|
46
71
|
|
|
47
|
-
##
|
|
72
|
+
## Configuration
|
|
48
73
|
|
|
49
|
-
|
|
74
|
+
```ts
|
|
75
|
+
const shipmail = new ShipMailClient({
|
|
76
|
+
apiKey: process.env.SHIPMAIL_API_KEY!,
|
|
77
|
+
baseUrl: "https://shipmail.to/api/v1",
|
|
78
|
+
maxRetries: 2,
|
|
79
|
+
timeout: 30_000,
|
|
80
|
+
fetch: customFetch,
|
|
81
|
+
defaultHeaders: { "x-app-name": "my-app" },
|
|
82
|
+
});
|
|
83
|
+
```
|
|
50
84
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
85
|
+
| Option | Type | Default | Description |
|
|
86
|
+
| ---------------- | ------------------------ | ---------------------------- | --------------------------------------------------------------- |
|
|
87
|
+
| `apiKey` | `string` | required | Shipmail API key (`sm_live_...`). |
|
|
88
|
+
| `baseUrl` | `string` | `https://shipmail.to/api/v1` | API base URL. |
|
|
89
|
+
| `maxRetries` | `number` | `2` | Retry count on 5xx and 429. Total attempts is `maxRetries + 1`. |
|
|
90
|
+
| `timeout` | `number` | `30_000` | Per-request timeout in ms. |
|
|
91
|
+
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation. |
|
|
92
|
+
| `defaultHeaders` | `Record<string, string>` | `{}` | Headers added to every request. |
|
|
93
|
+
|
|
94
|
+
## Domains
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
await shipmail.domains.create({ name: "example.com" });
|
|
98
|
+
await shipmail.domains.list({ limit: 10 });
|
|
99
|
+
await shipmail.domains.get("dom_...");
|
|
100
|
+
await shipmail.domains.update("dom_...", { catch_all_mailbox_id: "mbx_..." });
|
|
101
|
+
await shipmail.domains.delete("dom_...");
|
|
102
|
+
await shipmail.domains.verify("dom_...");
|
|
103
|
+
await shipmail.domains.search({ keyword: "example" });
|
|
104
|
+
await shipmail.domains.register({
|
|
105
|
+
name: "example.com",
|
|
106
|
+
years: 1,
|
|
107
|
+
contact: {
|
|
108
|
+
first_name: "Jane",
|
|
109
|
+
last_name: "Doe",
|
|
110
|
+
address1: "1 Main St",
|
|
111
|
+
/* ... */
|
|
112
|
+
},
|
|
113
|
+
});
|
|
58
114
|
```
|
|
59
115
|
|
|
60
|
-
|
|
116
|
+
## Mailboxes
|
|
61
117
|
|
|
62
|
-
```
|
|
63
|
-
|
|
118
|
+
```ts
|
|
119
|
+
await shipmail.mailboxes.create({
|
|
64
120
|
domain_id: "dom_...",
|
|
65
121
|
address: "hello",
|
|
66
122
|
display_name: "Hello",
|
|
67
123
|
});
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await
|
|
124
|
+
await shipmail.mailboxes.list({ domain_id: "dom_..." });
|
|
125
|
+
await shipmail.mailboxes.get("mbx_...");
|
|
126
|
+
await shipmail.mailboxes.update("mbx_...", { display_name: "New Name" });
|
|
127
|
+
await shipmail.mailboxes.resetPassword("mbx_...", { password: "NewPassword1" });
|
|
128
|
+
const folders = await shipmail.mailboxes.listFolders("mbx_...");
|
|
129
|
+
const folder = await shipmail.mailboxes.createFolder("mbx_...", {
|
|
130
|
+
name: "VIP",
|
|
131
|
+
parent_id: null,
|
|
132
|
+
});
|
|
133
|
+
await shipmail.mailboxes.updateFolder("mbx_...", folder.id, { name: "VIP Clients" });
|
|
134
|
+
await shipmail.mailboxes.deleteFolder("mbx_...", folder.id);
|
|
135
|
+
const identities = await shipmail.mailboxes.listIdentities("mbx_...");
|
|
136
|
+
const rules = await shipmail.mailboxes.getRules("mbx_...");
|
|
137
|
+
await shipmail.mailboxes.updateRules("mbx_...", { rules: rules.rules });
|
|
138
|
+
await shipmail.mailboxes.updateSpamFilter("mbx_...", { threshold: 8 });
|
|
139
|
+
await shipmail.mailboxes.delete("mbx_...");
|
|
140
|
+
|
|
141
|
+
await shipmail.mailboxes.updateAutoReply("mbx_...", {
|
|
142
|
+
enabled: true,
|
|
143
|
+
subject: "Out of office",
|
|
144
|
+
body: "Back on Monday.",
|
|
145
|
+
from_date: "2026-06-01",
|
|
146
|
+
to_date: "2026-06-07",
|
|
147
|
+
});
|
|
72
148
|
```
|
|
73
149
|
|
|
74
|
-
|
|
150
|
+
## Messages
|
|
75
151
|
|
|
76
|
-
```
|
|
77
|
-
|
|
152
|
+
```ts
|
|
153
|
+
await shipmail.messages.send({
|
|
78
154
|
mailbox_id: "mbx_...",
|
|
79
|
-
to: [{ address: "user@example.com"
|
|
155
|
+
to: [{ address: "user@example.com" }],
|
|
80
156
|
cc: [{ address: "cc@example.com" }],
|
|
81
157
|
subject: "Hello",
|
|
82
|
-
|
|
83
|
-
|
|
158
|
+
text: "Hi there",
|
|
159
|
+
html: "<p>Hi there</p>",
|
|
84
160
|
});
|
|
85
161
|
|
|
86
|
-
|
|
162
|
+
await shipmail.messages.list({ mailbox_id: "mbx_...", limit: 25 });
|
|
163
|
+
await shipmail.messages.get("msg_...");
|
|
164
|
+
|
|
165
|
+
await shipmail.messages.reply("msg_...", {
|
|
166
|
+
to: [{ address: "user@example.com" }],
|
|
167
|
+
text: "Thanks for your email.",
|
|
168
|
+
});
|
|
87
169
|
```
|
|
88
170
|
|
|
89
|
-
|
|
171
|
+
## Threads
|
|
90
172
|
|
|
91
|
-
```
|
|
92
|
-
const threads = await
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
173
|
+
```ts
|
|
174
|
+
const threads = await shipmail.threads.list({ mailbox_id: "mbx_..." });
|
|
175
|
+
const threadId = threads.data[0].id;
|
|
176
|
+
const thread = await shipmail.threads.get(threadId);
|
|
177
|
+
|
|
178
|
+
await shipmail.threads.reply(threadId, {
|
|
179
|
+
text: "Thanks for your email.",
|
|
97
180
|
});
|
|
98
181
|
```
|
|
99
182
|
|
|
100
|
-
|
|
183
|
+
## Webhooks
|
|
101
184
|
|
|
102
|
-
```
|
|
103
|
-
const webhook = await
|
|
185
|
+
```ts
|
|
186
|
+
const webhook = await shipmail.webhooks.create({
|
|
104
187
|
url: "https://example.com/webhook",
|
|
105
188
|
events: ["message.received", "message.sent"],
|
|
106
|
-
description: "
|
|
189
|
+
description: "Incoming email handler",
|
|
107
190
|
});
|
|
108
|
-
// webhook.secret is only
|
|
191
|
+
// webhook.secret is returned only on creation. Store it now.
|
|
192
|
+
|
|
193
|
+
await shipmail.webhooks.list();
|
|
194
|
+
await shipmail.webhooks.get("whk_...");
|
|
195
|
+
await shipmail.webhooks.update("whk_...", { active: false });
|
|
196
|
+
await shipmail.webhooks.delete("whk_...");
|
|
197
|
+
|
|
198
|
+
await shipmail.webhooks.rotateSecret("whk_...");
|
|
199
|
+
await shipmail.webhooks.test("whk_...");
|
|
200
|
+
await shipmail.webhooks.listDeliveries("whk_...");
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Supported event types:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
message.received
|
|
207
|
+
message.sent
|
|
208
|
+
message.delivered
|
|
209
|
+
message.bounced
|
|
210
|
+
message.complained
|
|
211
|
+
domain.verified
|
|
212
|
+
domain.verification_failed
|
|
213
|
+
domain.degraded
|
|
214
|
+
org.reputation_warning
|
|
215
|
+
org.sending_throttled
|
|
216
|
+
org.sending_suspended
|
|
217
|
+
org.reputation_recovered
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Suppressions
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
await shipmail.suppressions.list({ limit: 25 });
|
|
224
|
+
await shipmail.suppressions.remove("user@example.com");
|
|
225
|
+
```
|
|
109
226
|
|
|
110
|
-
|
|
111
|
-
const webhook = await client.webhooks.get("whk_...");
|
|
112
|
-
const updated = await client.webhooks.update("whk_...", { active: false });
|
|
113
|
-
await client.webhooks.delete("whk_...");
|
|
227
|
+
Auto-paginate:
|
|
114
228
|
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
229
|
+
```ts
|
|
230
|
+
for await (const item of shipmail.suppressions.listAutoPaginating({ limit: 100 })) {
|
|
231
|
+
console.log(item.email_address, item.reason);
|
|
232
|
+
}
|
|
118
233
|
```
|
|
119
234
|
|
|
120
|
-
|
|
235
|
+
## Status
|
|
121
236
|
|
|
122
|
-
```
|
|
123
|
-
const status = await
|
|
237
|
+
```ts
|
|
238
|
+
const status = await shipmail.status.get();
|
|
124
239
|
```
|
|
125
240
|
|
|
126
241
|
## Pagination
|
|
127
242
|
|
|
128
|
-
List methods return
|
|
243
|
+
List methods return `{ data, pagination }` with cursor-based pagination:
|
|
129
244
|
|
|
130
|
-
```
|
|
131
|
-
const page = await
|
|
132
|
-
|
|
133
|
-
|
|
245
|
+
```ts
|
|
246
|
+
const page = await shipmail.domains.list({ limit: 10 });
|
|
247
|
+
page.data; // Domain[]
|
|
248
|
+
page.pagination; // { next_cursor, has_more }
|
|
134
249
|
|
|
135
|
-
// Fetch next page
|
|
136
250
|
if (page.pagination.has_more) {
|
|
137
|
-
const next = await
|
|
251
|
+
const next = await shipmail.domains.list({
|
|
138
252
|
cursor: page.pagination.next_cursor,
|
|
139
253
|
limit: 10,
|
|
140
254
|
});
|
|
141
255
|
}
|
|
142
256
|
```
|
|
143
257
|
|
|
144
|
-
Auto-
|
|
258
|
+
Auto-paginate over all pages:
|
|
145
259
|
|
|
146
|
-
```
|
|
147
|
-
for await (const domain of
|
|
260
|
+
```ts
|
|
261
|
+
for await (const domain of shipmail.domains.listAutoPaginating({ limit: 25 })) {
|
|
148
262
|
console.log(domain.name);
|
|
149
263
|
}
|
|
150
264
|
```
|
|
151
265
|
|
|
152
|
-
|
|
266
|
+
`listAutoPaginating` is available on `domains`, `mailboxes`, `messages`, `threads`, `webhooks`, `webhooks.listDeliveriesAutoPaginating`, and `suppressions`.
|
|
267
|
+
|
|
268
|
+
## Webhook verification
|
|
153
269
|
|
|
154
270
|
Verify incoming webhook signatures without instantiating a client:
|
|
155
271
|
|
|
156
|
-
```
|
|
272
|
+
```ts
|
|
157
273
|
import { verifyWebhook, WebhookVerificationError } from "shipmail";
|
|
158
274
|
|
|
159
275
|
try {
|
|
160
276
|
const event = await verifyWebhook(rawBody, request.headers, webhookSecret);
|
|
161
|
-
|
|
162
|
-
|
|
277
|
+
event.event_type; // typed WebhookEventType union
|
|
278
|
+
event.data;
|
|
163
279
|
} catch (err) {
|
|
164
280
|
if (err instanceof WebhookVerificationError) {
|
|
165
|
-
|
|
281
|
+
// signature mismatch, missing header, expired timestamp, etc.
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Next.js Route Handler example
|
|
287
|
+
|
|
288
|
+
App Router consumes `request.text()` to get the raw body. Do not parse to JSON before verifying.
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
// app/api/webhooks/shipmail/route.ts
|
|
292
|
+
import { verifyWebhook, WebhookVerificationError } from "shipmail";
|
|
293
|
+
|
|
294
|
+
export async function POST(request: Request) {
|
|
295
|
+
const rawBody = await request.text();
|
|
296
|
+
const secret = process.env.SHIPMAIL_WEBHOOK_SECRET!;
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const event = await verifyWebhook(rawBody, request.headers, secret);
|
|
300
|
+
// handle event...
|
|
301
|
+
return new Response("ok");
|
|
302
|
+
} catch (err) {
|
|
303
|
+
if (err instanceof WebhookVerificationError) {
|
|
304
|
+
return new Response("invalid signature", { status: 401 });
|
|
305
|
+
}
|
|
306
|
+
throw err;
|
|
166
307
|
}
|
|
167
308
|
}
|
|
168
309
|
```
|
|
169
310
|
|
|
170
|
-
##
|
|
311
|
+
## Per-request options
|
|
312
|
+
|
|
313
|
+
Every method accepts a final `options` argument:
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
type MethodOptions = {
|
|
317
|
+
timeout?: number;
|
|
318
|
+
signal?: AbortSignal;
|
|
319
|
+
headers?: Record<string, string>;
|
|
320
|
+
idempotencyKey?: string;
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
await shipmail.messages.send(params, {
|
|
326
|
+
timeout: 5_000,
|
|
327
|
+
headers: { "x-trace-id": traceId },
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Idempotency
|
|
332
|
+
|
|
333
|
+
Pass `idempotencyKey` on mutating calls to make them safe to retry:
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
await shipmail.messages.send(
|
|
337
|
+
{
|
|
338
|
+
mailbox_id: "mbx_...",
|
|
339
|
+
to: [{ address: "user@example.com" }],
|
|
340
|
+
subject: "Receipt",
|
|
341
|
+
text: "Thanks for your purchase.",
|
|
342
|
+
},
|
|
343
|
+
{ idempotencyKey: `receipt-${orderId}` },
|
|
344
|
+
);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
The SDK adds the key as the `Idempotency-Key` header. Reuse the same key to retry without sending a duplicate email. Keys are scoped per API key.
|
|
348
|
+
|
|
349
|
+
## Cancellation
|
|
350
|
+
|
|
351
|
+
Pass an `AbortSignal` to cancel an in-flight request. The signal also cancels SDK-internal retries:
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
const controller = new AbortController();
|
|
355
|
+
setTimeout(() => controller.abort(), 2_000);
|
|
356
|
+
|
|
357
|
+
await shipmail.messages.send(params, { signal: controller.signal });
|
|
358
|
+
```
|
|
171
359
|
|
|
172
|
-
|
|
360
|
+
## Custom fetch and proxies
|
|
173
361
|
|
|
174
|
-
|
|
362
|
+
Inject a custom fetch implementation for proxies, observability, or testing:
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
const shipmail = new ShipMailClient({
|
|
366
|
+
apiKey: process.env.SHIPMAIL_API_KEY!,
|
|
367
|
+
fetch: async (url, init) => {
|
|
368
|
+
const start = Date.now();
|
|
369
|
+
const res = await fetch(url, init);
|
|
370
|
+
metrics.histogram("shipmail.fetch.duration_ms", Date.now() - start);
|
|
371
|
+
return res;
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The custom fetch receives the same arguments as the global `fetch` and must return a `Response`.
|
|
377
|
+
|
|
378
|
+
## Errors
|
|
379
|
+
|
|
380
|
+
The SDK throws typed errors that map to HTTP responses. All inherit from `ShipMailError`:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
175
383
|
import {
|
|
176
384
|
ShipMailError,
|
|
177
385
|
AuthenticationError,
|
|
178
386
|
AuthorizationError,
|
|
179
387
|
ValidationError,
|
|
180
388
|
NotFoundError,
|
|
181
|
-
RateLimitError,
|
|
182
389
|
ConflictError,
|
|
390
|
+
RateLimitError,
|
|
391
|
+
QuotaExceededError,
|
|
183
392
|
InternalServerError,
|
|
184
393
|
ConnectionError,
|
|
185
394
|
} from "shipmail";
|
|
186
395
|
|
|
187
396
|
try {
|
|
188
|
-
await
|
|
397
|
+
await shipmail.messages.send(params);
|
|
189
398
|
} catch (err) {
|
|
190
399
|
if (err instanceof ValidationError) {
|
|
191
|
-
|
|
192
|
-
|
|
400
|
+
err.message;
|
|
401
|
+
err.details; // field-level validation errors
|
|
193
402
|
}
|
|
194
403
|
if (err instanceof RateLimitError) {
|
|
195
|
-
|
|
404
|
+
err.retryAfter; // seconds
|
|
196
405
|
}
|
|
197
406
|
if (err instanceof ShipMailError) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
407
|
+
err.status; // HTTP status
|
|
408
|
+
err.type; // error type string
|
|
409
|
+
err.requestId; // include this when contacting support
|
|
410
|
+
err.retryable;
|
|
202
411
|
}
|
|
412
|
+
throw err;
|
|
203
413
|
}
|
|
204
414
|
```
|
|
205
415
|
|
|
416
|
+
| Error | When |
|
|
417
|
+
| --------------------- | -------------------------------------------------------------- |
|
|
418
|
+
| `AuthenticationError` | 401. Bad or missing API key. |
|
|
419
|
+
| `AuthorizationError` | 403. Key lacks permission for the resource. |
|
|
420
|
+
| `ValidationError` | 400 or 422. See `details` for per-field errors. |
|
|
421
|
+
| `NotFoundError` | 404. |
|
|
422
|
+
| `ConflictError` | 409. Resource already exists or state conflict. |
|
|
423
|
+
| `RateLimitError` | 429. Read `retryAfter` (seconds). |
|
|
424
|
+
| `QuotaExceededError` | 402. Plan or sending quota exceeded. |
|
|
425
|
+
| `InternalServerError` | 5xx. Retried automatically up to `maxRetries`. |
|
|
426
|
+
| `ConnectionError` | Network error, timeout, or DNS failure. Retried automatically. |
|
|
427
|
+
|
|
206
428
|
## Retries
|
|
207
429
|
|
|
208
|
-
The SDK
|
|
430
|
+
The SDK retries on `5xx`, `429`, and connection errors with exponential backoff and jitter. `Retry-After` is honored when present. Default is 2 retries (3 total attempts).
|
|
209
431
|
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
432
|
+
```ts
|
|
433
|
+
new ShipMailClient({ apiKey, maxRetries: 0 }); // disable retries
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Retries respect any `AbortSignal` you pass via `MethodOptions.signal`.
|
|
437
|
+
|
|
438
|
+
## Bundling
|
|
439
|
+
|
|
440
|
+
The package ships ESM and CommonJS via `exports`, with `"sideEffects": false` for tree-shaking. Importing a single resource pulls in only what it needs. The published bundle has no runtime dependencies.
|
|
441
|
+
|
|
442
|
+
## Testing
|
|
443
|
+
|
|
444
|
+
Mock by injecting a custom `fetch` at construction time:
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
const shipmail = new ShipMailClient({
|
|
448
|
+
apiKey: "sm_live_test",
|
|
449
|
+
fetch: async () =>
|
|
450
|
+
new Response(JSON.stringify({ id: "msg_123", status: "queued" }), {
|
|
451
|
+
status: 200,
|
|
452
|
+
headers: { "content-type": "application/json" },
|
|
453
|
+
}),
|
|
214
454
|
});
|
|
215
455
|
```
|
|
216
456
|
|
|
457
|
+
This is the recommended pattern for unit tests. No HTTP interception or mocking library required.
|
|
458
|
+
|
|
217
459
|
## License
|
|
218
460
|
|
|
219
|
-
MIT
|
|
461
|
+
[MIT](./LICENSE).
|
|
462
|
+
|
|
463
|
+
## Links
|
|
464
|
+
|
|
465
|
+
- [Shipmail docs](https://shipmail.to/docs)
|
|
466
|
+
- [Quick start](https://shipmail.to/docs/quick-start)
|
|
467
|
+
- [API reference](https://shipmail.to/docs/api)
|
|
468
|
+
- [TypeScript SDK guide](https://shipmail.to/docs/sdks/typescript)
|
|
469
|
+
- [`shipmail-mcp` (MCP server)](https://www.npmjs.com/package/shipmail-mcp)
|
|
470
|
+
- [Changelog](./CHANGELOG.md)
|
|
471
|
+
- [Issues](https://github.com/jcoulaud/ShipMail/issues)
|