shipmail 0.1.5 → 0.1.19

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/dist/index.js CHANGED
@@ -1,5 +1,3 @@
1
- import { timingSafeEqual, createHmac } from 'crypto';
2
-
3
1
  // src/errors.ts
4
2
  var ShipMailError = class extends Error {
5
3
  constructor(message, options) {
@@ -35,6 +33,12 @@ var ValidationError = class extends ShipMailError {
35
33
  this.details = options?.details;
36
34
  }
37
35
  };
36
+ var QuotaExceededError = class extends ShipMailError {
37
+ constructor(message, requestId) {
38
+ super(message, { status: 403, type: "quota_exceeded", requestId, retryable: false });
39
+ this.name = "QuotaExceededError";
40
+ }
41
+ };
38
42
  var NotFoundError = class extends ShipMailError {
39
43
  constructor(message, requestId) {
40
44
  super(message, { status: 404, type: "not_found", requestId, retryable: false });
@@ -85,6 +89,8 @@ function mapErrorFromResponse(status, body, requestId) {
85
89
  return new AuthenticationError(error.message, rid);
86
90
  case "authorization_error":
87
91
  return new AuthorizationError(error.message, rid);
92
+ case "quota_exceeded":
93
+ return new QuotaExceededError(error.message, rid);
88
94
  case "validation_error":
89
95
  return new ValidationError(error.message, { requestId: rid, details: error.details });
90
96
  case "not_found":
@@ -138,21 +144,23 @@ var ApiResource = class {
138
144
 
139
145
  // src/resources/domains.ts
140
146
  var Domains = class extends ApiResource {
141
- create(params) {
147
+ create(params, options) {
142
148
  return this.client.request({
143
149
  method: "POST",
144
150
  path: "/domains",
145
- body: params
151
+ body: params,
152
+ methodOptions: options
146
153
  });
147
154
  }
148
- list(params) {
155
+ list(params, options) {
149
156
  return this.client.request({
150
157
  method: "GET",
151
158
  path: "/domains",
152
159
  query: {
153
160
  cursor: params?.cursor,
154
161
  limit: params?.limit
155
- }
162
+ },
163
+ methodOptions: options
156
164
  });
157
165
  }
158
166
  listAutoPaginating(params) {
@@ -160,43 +168,64 @@ var Domains = class extends ApiResource {
160
168
  limit: params?.limit
161
169
  });
162
170
  }
163
- get(id) {
171
+ get(id, options) {
164
172
  return this.client.request({
165
173
  method: "GET",
166
- path: `/domains/${id}`
174
+ path: `/domains/${encodeURIComponent(id)}`,
175
+ methodOptions: options
167
176
  });
168
177
  }
169
- update(id, params) {
178
+ update(id, params, options) {
170
179
  return this.client.request({
171
180
  method: "PATCH",
172
- path: `/domains/${id}`,
173
- body: params
181
+ path: `/domains/${encodeURIComponent(id)}`,
182
+ body: params,
183
+ methodOptions: options
174
184
  });
175
185
  }
176
- delete(id) {
186
+ delete(id, options) {
177
187
  return this.client.request({
178
188
  method: "DELETE",
179
- path: `/domains/${id}`
189
+ path: `/domains/${encodeURIComponent(id)}`,
190
+ methodOptions: options
191
+ });
192
+ }
193
+ verify(id, options) {
194
+ return this.client.request({
195
+ method: "POST",
196
+ path: `/domains/${encodeURIComponent(id)}/verification`,
197
+ methodOptions: options
180
198
  });
181
199
  }
182
- verify(id) {
200
+ search(params, options) {
183
201
  return this.client.request({
184
202
  method: "POST",
185
- path: `/domains/${id}/verification`
203
+ path: "/domains/search",
204
+ body: params,
205
+ methodOptions: options
206
+ });
207
+ }
208
+ register(params, options) {
209
+ return this.client.request({
210
+ method: "POST",
211
+ path: "/domains/register",
212
+ body: params,
213
+ methodOptions: options
186
214
  });
187
215
  }
188
216
  };
189
217
 
190
218
  // src/resources/mailboxes.ts
191
219
  var Mailboxes = class extends ApiResource {
192
- create(params) {
220
+ create(params, options) {
193
221
  return this.client.request({
194
222
  method: "POST",
195
223
  path: "/mailboxes",
196
- body: params
224
+ body: params,
225
+ methodOptions: options
197
226
  });
198
227
  }
199
- list(params) {
228
+ list(params, options) {
200
229
  return this.client.request({
201
230
  method: "GET",
202
231
  path: "/mailboxes",
@@ -204,7 +233,8 @@ var Mailboxes = class extends ApiResource {
204
233
  domain_id: params?.domain_id,
205
234
  cursor: params?.cursor,
206
235
  limit: params?.limit
207
- }
236
+ },
237
+ methodOptions: options
208
238
  });
209
239
  }
210
240
  listAutoPaginating(params) {
@@ -213,71 +243,124 @@ var Mailboxes = class extends ApiResource {
213
243
  limit: params?.limit
214
244
  });
215
245
  }
216
- get(id) {
246
+ get(id, options) {
217
247
  return this.client.request({
218
248
  method: "GET",
219
- path: `/mailboxes/${id}`
249
+ path: `/mailboxes/${encodeURIComponent(id)}`,
250
+ methodOptions: options
220
251
  });
221
252
  }
222
- update(id, params) {
253
+ update(id, params, options) {
223
254
  return this.client.request({
224
255
  method: "PATCH",
225
- path: `/mailboxes/${id}`,
226
- body: params
256
+ path: `/mailboxes/${encodeURIComponent(id)}`,
257
+ body: params,
258
+ methodOptions: options
227
259
  });
228
260
  }
229
- delete(id) {
261
+ delete(id, options) {
230
262
  return this.client.request({
231
263
  method: "DELETE",
232
- path: `/mailboxes/${id}`
233
- });
234
- }
235
- uploadAvatar(id, data, contentType) {
236
- return this.client.request({
237
- method: "PUT",
238
- path: `/mailboxes/${id}/avatar`,
239
- rawBody: data,
240
- contentType
264
+ path: `/mailboxes/${encodeURIComponent(id)}`,
265
+ methodOptions: options
241
266
  });
242
267
  }
243
- deleteAvatar(id) {
268
+ updateAutoReply(id, params, options) {
244
269
  return this.client.request({
245
- method: "DELETE",
246
- path: `/mailboxes/${id}/avatar`
270
+ method: "PATCH",
271
+ path: `/mailboxes/${encodeURIComponent(id)}/auto-reply`,
272
+ body: params,
273
+ methodOptions: options
247
274
  });
248
275
  }
249
276
  };
250
277
 
251
278
  // src/resources/messages.ts
252
279
  var Messages = class extends ApiResource {
253
- send(params) {
280
+ send(params, options) {
254
281
  return this.client.request({
255
282
  method: "POST",
256
283
  path: "/messages",
257
- body: params
284
+ body: params,
285
+ methodOptions: options
258
286
  });
259
287
  }
260
- get(id) {
288
+ list(params, options) {
261
289
  return this.client.request({
262
290
  method: "GET",
263
- path: `/messages/${id}`
291
+ path: "/messages",
292
+ query: {
293
+ mailbox_id: params.mailbox_id,
294
+ cursor: params.cursor,
295
+ limit: params.limit
296
+ },
297
+ methodOptions: options
298
+ });
299
+ }
300
+ listAutoPaginating(params) {
301
+ return new Page(this.client, "/messages", {
302
+ mailbox_id: params.mailbox_id,
303
+ limit: params.limit
304
+ });
305
+ }
306
+ get(id, options) {
307
+ return this.client.request({
308
+ method: "GET",
309
+ path: `/messages/${encodeURIComponent(id)}`,
310
+ methodOptions: options
311
+ });
312
+ }
313
+ reply(id, params, options) {
314
+ return this.client.request({
315
+ method: "POST",
316
+ path: `/messages/${encodeURIComponent(id)}/reply`,
317
+ body: params,
318
+ methodOptions: options
264
319
  });
265
320
  }
266
321
  };
267
322
 
268
323
  // src/resources/status.ts
269
324
  var Status = class extends ApiResource {
270
- get() {
325
+ get(options) {
326
+ return this.client.request({
327
+ method: "GET",
328
+ path: "/status",
329
+ methodOptions: options
330
+ });
331
+ }
332
+ };
333
+
334
+ // src/resources/suppressions.ts
335
+ var Suppressions = class extends ApiResource {
336
+ list(params, options) {
271
337
  return this.client.request({
272
338
  method: "GET",
273
- path: "/status"
339
+ path: "/suppressions",
340
+ query: {
341
+ cursor: params?.cursor,
342
+ limit: params?.limit
343
+ },
344
+ methodOptions: options
345
+ });
346
+ }
347
+ listAutoPaginating(params) {
348
+ return new Page(this.client, "/suppressions", {
349
+ limit: params?.limit
350
+ });
351
+ }
352
+ remove(email, options) {
353
+ return this.client.request({
354
+ method: "DELETE",
355
+ path: `/suppressions/${encodeURIComponent(email)}`,
356
+ methodOptions: options
274
357
  });
275
358
  }
276
359
  };
277
360
 
278
361
  // src/resources/threads.ts
279
362
  var Threads = class extends ApiResource {
280
- list(params) {
363
+ list(params, options) {
281
364
  return this.client.request({
282
365
  method: "GET",
283
366
  path: "/threads",
@@ -285,7 +368,8 @@ var Threads = class extends ApiResource {
285
368
  mailbox_id: params.mailbox_id,
286
369
  cursor: params.cursor,
287
370
  limit: params.limit
288
- }
371
+ },
372
+ methodOptions: options
289
373
  });
290
374
  }
291
375
  listAutoPaginating(params) {
@@ -294,42 +378,46 @@ var Threads = class extends ApiResource {
294
378
  limit: params.limit
295
379
  });
296
380
  }
297
- get(id, params) {
381
+ get(id, params, options) {
298
382
  return this.client.request({
299
383
  method: "GET",
300
- path: `/threads/${id}`,
384
+ path: `/threads/${encodeURIComponent(id)}`,
301
385
  query: {
302
386
  cursor: params?.cursor,
303
387
  limit: params?.limit
304
- }
388
+ },
389
+ methodOptions: options
305
390
  });
306
391
  }
307
- reply(id, params) {
392
+ reply(id, params, options) {
308
393
  return this.client.request({
309
394
  method: "POST",
310
- path: `/threads/${id}/reply`,
311
- body: params
395
+ path: `/threads/${encodeURIComponent(id)}/reply`,
396
+ body: params,
397
+ methodOptions: options
312
398
  });
313
399
  }
314
400
  };
315
401
 
316
402
  // src/resources/webhooks.ts
317
403
  var Webhooks = class extends ApiResource {
318
- create(params) {
404
+ create(params, options) {
319
405
  return this.client.request({
320
406
  method: "POST",
321
407
  path: "/webhooks",
322
- body: params
408
+ body: params,
409
+ methodOptions: options
323
410
  });
324
411
  }
325
- list(params) {
412
+ list(params, options) {
326
413
  return this.client.request({
327
414
  method: "GET",
328
415
  path: "/webhooks",
329
416
  query: {
330
417
  cursor: params?.cursor,
331
418
  limit: params?.limit
332
- }
419
+ },
420
+ methodOptions: options
333
421
  });
334
422
  }
335
423
  listAutoPaginating(params) {
@@ -337,69 +425,82 @@ var Webhooks = class extends ApiResource {
337
425
  limit: params?.limit
338
426
  });
339
427
  }
340
- get(id) {
428
+ get(id, options) {
341
429
  return this.client.request({
342
430
  method: "GET",
343
- path: `/webhooks/${id}`
431
+ path: `/webhooks/${encodeURIComponent(id)}`,
432
+ methodOptions: options
344
433
  });
345
434
  }
346
- update(id, params) {
435
+ update(id, params, options) {
347
436
  return this.client.request({
348
437
  method: "PATCH",
349
- path: `/webhooks/${id}`,
350
- body: params
438
+ path: `/webhooks/${encodeURIComponent(id)}`,
439
+ body: params,
440
+ methodOptions: options
351
441
  });
352
442
  }
353
- delete(id) {
443
+ delete(id, options) {
354
444
  return this.client.request({
355
445
  method: "DELETE",
356
- path: `/webhooks/${id}`
446
+ path: `/webhooks/${encodeURIComponent(id)}`,
447
+ methodOptions: options
357
448
  });
358
449
  }
359
- rotateSecret(id) {
450
+ rotateSecret(id, options) {
360
451
  return this.client.request({
361
452
  method: "POST",
362
- path: `/webhooks/${id}/rotate-secret`
453
+ path: `/webhooks/${encodeURIComponent(id)}/rotate-secret`,
454
+ methodOptions: options
363
455
  });
364
456
  }
365
- test(id) {
457
+ test(id, options) {
366
458
  return this.client.request({
367
459
  method: "POST",
368
- path: `/webhooks/${id}/test`
460
+ path: `/webhooks/${encodeURIComponent(id)}/test`,
461
+ methodOptions: options
369
462
  });
370
463
  }
371
- listDeliveries(id, params) {
464
+ listDeliveries(id, params, options) {
372
465
  return this.client.request({
373
466
  method: "GET",
374
- path: `/webhooks/${id}/deliveries`,
467
+ path: `/webhooks/${encodeURIComponent(id)}/deliveries`,
375
468
  query: {
376
469
  status: params?.status,
377
470
  event_type: params?.event_type,
378
471
  cursor: params?.cursor,
379
472
  limit: params?.limit
380
- }
473
+ },
474
+ methodOptions: options
381
475
  });
382
476
  }
383
477
  listDeliveriesAutoPaginating(id, params) {
384
- return new Page(this.client, `/webhooks/${id}/deliveries`, {
385
- status: params?.status,
386
- event_type: params?.event_type,
387
- limit: params?.limit
388
- });
478
+ return new Page(
479
+ this.client,
480
+ `/webhooks/${encodeURIComponent(id)}/deliveries`,
481
+ {
482
+ status: params?.status,
483
+ event_type: params?.event_type,
484
+ limit: params?.limit
485
+ }
486
+ );
389
487
  }
390
488
  };
391
489
 
392
490
  // src/version.ts
393
- var VERSION = "0.1.5";
491
+ var VERSION = "0.1.19";
394
492
 
395
493
  // src/client.ts
396
- var DEFAULT_BASE_URL = "https://api.shipmail.to/v1";
494
+ var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
397
495
  var DEFAULT_MAX_RETRIES = 2;
398
496
  var DEFAULT_TIMEOUT = 3e4;
399
497
  function isRetryableStatus(status) {
400
498
  return status === 429 || status >= 500;
401
499
  }
402
- function calculateBackoff(attempt) {
500
+ function calculateBackoff(attempt, retryAfterMs) {
501
+ if (retryAfterMs !== void 0 && retryAfterMs > 0) {
502
+ return retryAfterMs;
503
+ }
403
504
  const base = Math.min(2 ** attempt * 500, 8e3);
404
505
  const jitter = Math.random() * base * 0.5;
405
506
  return base + jitter;
@@ -412,6 +513,10 @@ function isApiErrorBody(value) {
412
513
  return typeof error["type"] === "string" && typeof error["message"] === "string";
413
514
  }
414
515
  var ShipMailClient = class {
516
+ /**
517
+ * Create a new ShipMail client.
518
+ * @param config - API key string or full configuration object.
519
+ */
415
520
  constructor(config) {
416
521
  if (typeof config === "string") {
417
522
  this.apiKey = config;
@@ -419,16 +524,19 @@ var ShipMailClient = class {
419
524
  this.maxRetries = DEFAULT_MAX_RETRIES;
420
525
  this.timeout = DEFAULT_TIMEOUT;
421
526
  this.fetchFn = globalThis.fetch;
527
+ this.defaultHeaders = {};
422
528
  } else {
423
529
  this.apiKey = config.apiKey;
424
530
  this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
425
531
  this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
426
532
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
427
533
  this.fetchFn = config.fetch ?? globalThis.fetch;
534
+ this.defaultHeaders = config.defaultHeaders ? { ...config.defaultHeaders } : {};
428
535
  }
429
536
  this.domains = new Domains(this);
430
537
  this.mailboxes = new Mailboxes(this);
431
538
  this.messages = new Messages(this);
539
+ this.suppressions = new Suppressions(this);
432
540
  this.threads = new Threads(this);
433
541
  this.webhooks = new Webhooks(this);
434
542
  this.status = new Status(this);
@@ -436,18 +544,25 @@ var ShipMailClient = class {
436
544
  async request(options) {
437
545
  const url = this.buildUrl(options.path, options.query);
438
546
  let lastError;
547
+ const methodOpts = options.methodOptions;
439
548
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
440
549
  if (attempt > 0) {
441
- const delay = calculateBackoff(attempt - 1);
550
+ const retryAfterMs = lastError instanceof RateLimitError && lastError.retryAfter !== void 0 ? lastError.retryAfter * 1e3 : void 0;
551
+ const delay = calculateBackoff(attempt - 1, retryAfterMs);
442
552
  await new Promise((resolve) => setTimeout(resolve, delay));
443
553
  }
444
554
  let response;
445
555
  try {
446
556
  const headers = {
557
+ ...this.defaultHeaders,
447
558
  Authorization: `Bearer ${this.apiKey}`,
448
559
  "User-Agent": `shipmail-node/${VERSION}`,
449
- Accept: "application/json"
560
+ Accept: "application/json",
561
+ ...methodOpts?.headers
450
562
  };
563
+ if (methodOpts?.idempotencyKey) {
564
+ headers["Idempotency-Key"] = methodOpts.idempotencyKey;
565
+ }
451
566
  let body = null;
452
567
  if (options.rawBody !== void 0) {
453
568
  headers["Content-Type"] = options.contentType ?? "application/octet-stream";
@@ -456,11 +571,12 @@ var ShipMailClient = class {
456
571
  headers["Content-Type"] = "application/json";
457
572
  body = JSON.stringify(options.body);
458
573
  }
574
+ const effectiveTimeout = methodOpts?.timeout ?? this.timeout;
459
575
  response = await this.fetchFn(url, {
460
576
  method: options.method,
461
577
  headers,
462
578
  body,
463
- signal: AbortSignal.timeout(this.timeout)
579
+ signal: methodOpts?.signal ?? AbortSignal.timeout(effectiveTimeout)
464
580
  });
465
581
  } catch (err) {
466
582
  lastError = new ConnectionError(err instanceof Error ? err.message : "Request failed");
@@ -509,15 +625,31 @@ var ShipMailClient = class {
509
625
  // src/types/common.ts
510
626
  var WEBHOOK_EVENT_TYPES = [
511
627
  "message.received",
512
- "message.queued",
513
628
  "message.sent",
514
629
  "message.delivered",
515
630
  "message.bounced",
516
631
  "message.complained",
517
632
  "domain.verified",
518
633
  "domain.verification_failed",
519
- "domain.degraded"
634
+ "domain.degraded",
635
+ "org.reputation_warning",
636
+ "org.sending_throttled",
637
+ "org.sending_suspended",
638
+ "org.reputation_recovered"
520
639
  ];
640
+ var DOMAIN_STATUSES = ["pending", "verifying", "verified", "degraded", "failed"];
641
+ var MESSAGE_STATUSES = [
642
+ "queued",
643
+ "sent",
644
+ "delivered",
645
+ "bounced",
646
+ "complained",
647
+ "failed"
648
+ ];
649
+ var MESSAGE_SOURCES = ["api", "dashboard", "inbound", "smtp"];
650
+ var WEBHOOK_DELIVERY_STATUSES = ["pending", "delivered", "failed"];
651
+
652
+ // src/webhook-verification.ts
521
653
  var DEFAULT_TOLERANCE_SECONDS = 300;
522
654
  function getHeader(headers, name) {
523
655
  if (headers instanceof Headers) {
@@ -525,12 +657,13 @@ function getHeader(headers, name) {
525
657
  }
526
658
  return headers[name] ?? headers[name.toLowerCase()];
527
659
  }
528
- function signPayload(secret, timestamp, body) {
660
+ async function signPayload(secret, timestamp, body) {
661
+ const { createHmac } = await import('crypto');
529
662
  const payload = `v1=${timestamp}
530
663
  ${body}`;
531
664
  return createHmac("sha256", secret).update(payload).digest("hex");
532
665
  }
533
- function verifyWebhook(body, headers, secret, options) {
666
+ async function verifyWebhook(body, headers, secret, options) {
534
667
  const signature = getHeader(headers, "x-shipmail-signature");
535
668
  const timestampStr = getHeader(headers, "x-shipmail-timestamp");
536
669
  if (!signature) {
@@ -548,7 +681,8 @@ function verifyWebhook(body, headers, secret, options) {
548
681
  if (Math.abs(now - timestamp) > tolerance) {
549
682
  throw new WebhookVerificationError("Webhook timestamp is outside tolerance window");
550
683
  }
551
- const expected = signPayload(secret, timestamp, body);
684
+ const expected = await signPayload(secret, timestamp, body);
685
+ const { timingSafeEqual } = await import('crypto');
552
686
  const sigBuffer = Buffer.from(signature, "hex");
553
687
  const expectedBuffer = Buffer.from(expected, "hex");
554
688
  if (sigBuffer.length !== expectedBuffer.length || !timingSafeEqual(sigBuffer, expectedBuffer)) {
@@ -563,6 +697,6 @@ function verifyWebhook(body, headers, secret, options) {
563
697
  return parsed;
564
698
  }
565
699
 
566
- export { AuthenticationError, AuthorizationError, ConflictError, ConnectionError, InternalServerError, NotFoundError, Page, RateLimitError, ShipMailClient, ShipMailError, ValidationError, WEBHOOK_EVENT_TYPES, WebhookVerificationError, ShipMailClient as default, verifyWebhook };
700
+ export { AuthenticationError, AuthorizationError, ConflictError, ConnectionError, DOMAIN_STATUSES, InternalServerError, MESSAGE_SOURCES, MESSAGE_STATUSES, NotFoundError, Page, QuotaExceededError, RateLimitError, ShipMailClient, ShipMailError, ValidationError, WEBHOOK_DELIVERY_STATUSES, WEBHOOK_EVENT_TYPES, WebhookVerificationError, ShipMailClient as default, verifyWebhook };
567
701
  //# sourceMappingURL=index.js.map
568
702
  //# sourceMappingURL=index.js.map