shipmail 0.5.5 → 0.5.7

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.d.cts CHANGED
@@ -882,6 +882,10 @@ type OutboundHeader = {
882
882
  type MessageMetadata = Readonly<Record<string, string | number | boolean | null>>;
883
883
  type SendMessageParams = {
884
884
  readonly mailbox_id?: string | undefined;
885
+ /**
886
+ * Sending mailbox: `hello@example.com` or `Acme Support <hello@example.com>`.
887
+ * For a transactional sender, pass the bare sender address. Shipmail adds its name and Reply-To.
888
+ */
885
889
  readonly from?: string | undefined;
886
890
  readonly to: readonly EmailRecipientInput[];
887
891
  readonly cc?: readonly EmailRecipientInput[] | undefined;
package/dist/index.d.ts CHANGED
@@ -882,6 +882,10 @@ type OutboundHeader = {
882
882
  type MessageMetadata = Readonly<Record<string, string | number | boolean | null>>;
883
883
  type SendMessageParams = {
884
884
  readonly mailbox_id?: string | undefined;
885
+ /**
886
+ * Sending mailbox: `hello@example.com` or `Acme Support <hello@example.com>`.
887
+ * For a transactional sender, pass the bare sender address. Shipmail adds its name and Reply-To.
888
+ */
885
889
  readonly from?: string | undefined;
886
890
  readonly to: readonly EmailRecipientInput[];
887
891
  readonly cc?: readonly EmailRecipientInput[] | undefined;
package/dist/index.js CHANGED
@@ -1695,17 +1695,26 @@ var Webhooks = class extends ApiResource {
1695
1695
  };
1696
1696
 
1697
1697
  // src/version.ts
1698
- var VERSION = "0.5.5";
1698
+ var VERSION = "0.5.7";
1699
1699
 
1700
1700
  // src/client.ts
1701
1701
  var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
1702
1702
  var DEFAULT_MAX_RETRIES = 2;
1703
1703
  var DEFAULT_TIMEOUT = 3e4;
1704
+ var MAX_RETRY_AFTER_MS = 6e4;
1705
+ function parseRetryAfter(value) {
1706
+ if (!value?.trim()) return void 0;
1707
+ const seconds = Number(value);
1708
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
1709
+ if (/^[+-]?[\d.]+$/.test(value.trim())) return void 0;
1710
+ const date = Date.parse(value);
1711
+ return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
1712
+ }
1704
1713
  function isRetryableStatus(status) {
1705
1714
  return status === 429 || status >= 500;
1706
1715
  }
1707
1716
  function calculateBackoff(attempt, retryAfterMs) {
1708
- if (retryAfterMs !== void 0 && retryAfterMs > 0) {
1717
+ if (retryAfterMs !== void 0 && Number.isFinite(retryAfterMs) && retryAfterMs >= 0) {
1709
1718
  return retryAfterMs;
1710
1719
  }
1711
1720
  const base2 = Math.min(2 ** attempt * 500, 8e3);
@@ -1748,6 +1757,9 @@ var ShipmailClient = class {
1748
1757
  this.defaultHeaders = config.defaultHeaders ? { ...config.defaultHeaders } : {};
1749
1758
  this.organizationId = config.organizationId;
1750
1759
  }
1760
+ if (!Number.isSafeInteger(this.maxRetries) || this.maxRetries < 0) {
1761
+ throw new RangeError("maxRetries must be a non-negative safe integer");
1762
+ }
1751
1763
  this.audiences = new Audiences(this);
1752
1764
  this.automations = new Automations(this);
1753
1765
  this.bookingPages = new BookingPages(this);
@@ -1783,9 +1795,13 @@ var ShipmailClient = class {
1783
1795
  const url = this.buildUrl(options.path, options.query);
1784
1796
  let lastError;
1785
1797
  const methodOpts = options.methodOptions;
1798
+ const retryHeaders = new Headers({ ...this.defaultHeaders, ...methodOpts?.headers });
1799
+ const method = options.method.toUpperCase();
1800
+ const idempotencyKey = methodOpts?.idempotencyKey || retryHeaders.get("Idempotency-Key");
1801
+ const canRetry = ["GET", "HEAD", "OPTIONS"].includes(method) || ["POST", "PATCH", "PUT"].includes(method) && Boolean(idempotencyKey?.trim());
1802
+ let retryAfterMs;
1786
1803
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
1787
1804
  if (attempt > 0) {
1788
- const retryAfterMs = lastError instanceof RateLimitError && lastError.retryAfter !== void 0 ? lastError.retryAfter * 1e3 : void 0;
1789
1805
  const delay = calculateBackoff(attempt - 1, retryAfterMs);
1790
1806
  await new Promise((resolve) => setTimeout(resolve, delay));
1791
1807
  }
@@ -1822,7 +1838,10 @@ var ShipmailClient = class {
1822
1838
  });
1823
1839
  } catch (err) {
1824
1840
  lastError = new ConnectionError(err instanceof Error ? err.message : "Request failed");
1825
- if (attempt < this.maxRetries) continue;
1841
+ if (canRetry && !methodOpts?.signal?.aborted && attempt < this.maxRetries) {
1842
+ retryAfterMs = void 0;
1843
+ continue;
1844
+ }
1826
1845
  throw lastError;
1827
1846
  }
1828
1847
  if (response.ok) {
@@ -1842,7 +1861,14 @@ var ShipmailClient = class {
1842
1861
  retryable: isRetryableStatus(response.status)
1843
1862
  });
1844
1863
  }
1845
- if (!lastError.retryable || attempt >= this.maxRetries) {
1864
+ retryAfterMs = parseRetryAfter(response.headers.get("Retry-After"));
1865
+ if (retryAfterMs === void 0 && lastError instanceof RateLimitError) {
1866
+ const seconds = lastError.retryAfter;
1867
+ if (typeof seconds === "number" && Number.isFinite(seconds) && seconds >= 0) {
1868
+ retryAfterMs = seconds * 1e3;
1869
+ }
1870
+ }
1871
+ if (!canRetry || !lastError.retryable || attempt >= this.maxRetries || retryAfterMs !== void 0 && retryAfterMs > MAX_RETRY_AFTER_MS) {
1846
1872
  throw lastError;
1847
1873
  }
1848
1874
  }