> ## Documentation Index
> Fetch the complete documentation index at: https://nuntly.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Advanced use of the Nuntly TypeScript SDK: client cloning, lifecycle hooks, retries, safe mode, raw responses, abort, logging, custom fetch and proxies.

The [README](https://github.com/nuntly/nuntly-sdk-typescript#readme) covers installation, configuration, pagination, errors and webhooks. This page covers the rest of the SDK. Every example assumes a client:

```ts theme={null}
import { Nuntly } from '@nuntly/sdk';

const nuntly = new Nuntly({ apiKey: process.env.NUNTLY_API_KEY });
```

## Client cloning

`withOptions(partial)` returns a new client built from the original one's options. The original client is untouched, and the new one reuses its hooks and retry settings. `defaultHeaders` is merged with the original headers; any other option you pass replaces the original value.

`defaultHeaders` is sent on every request, but Nuntly does not read or route on it. Use it for your own tracing and audit chain.

### One API key per customer

When each customer of your SaaS has their own Nuntly API key, a new `Nuntly` per request would need the hooks and the retry settings passed again. Clone the root client instead:

```ts theme={null}
const tenant = nuntly.withOptions({ apiKey: tenantApiKey });
await tenant.emails.send({ from, to, subject, html });
```

### One trace ID across services

When a request in your app calls several services, forward your trace ID on every Nuntly call so your APM links the email send to the request that triggered it:

```ts theme={null}
const traced = nuntly.withOptions({
  defaultHeaders: { 'X-Trace-Id': traceId },
});
await traced.emails.send({ from, to, subject, html });
```

### Who sent this email

To record which user of your app sent an email, stamp a header on every request. Your gateway, proxy or log pipeline records it; Nuntly ignores it.

```ts theme={null}
const audited = nuntly.withOptions({
  defaultHeaders: { 'X-Initiated-By': `user:${userId}` },
});
```

## Lifecycle hooks

Hooks run around each HTTP request, for logging or telemetry. A hook reads the request and cannot change it. Every hook receives a single `ctx` argument:

```ts theme={null}
import { APIError, Nuntly } from '@nuntly/sdk';

const nuntly = new Nuntly({
  apiKey: process.env.NUNTLY_API_KEY,
  hooks: {
    onRequest: (ctx) => {
      console.log(`-> ${ctx.method} ${ctx.url}`);
    },
    onResponse: (ctx) => {
      console.log(`<- ${ctx.response.status} ${ctx.request.path}`);
    },
    onSuccess: (ctx) => {
      metrics.increment('api.success');
    },
    onRetry: (ctx) => {
      console.warn(`Retry #${ctx.attempt} ${ctx.request.path}`);
    },
    onError: (ctx) => {
      if (ctx.error instanceof APIError) {
        sentry.captureException(ctx.error, { tags: { code: ctx.error.code } });
      } else {
        sentry.captureException(ctx.error);
      }
    },
  },
});
```

| Hook | Signature | When |
| - | - | - |
| `onRequest` | `(ctx: RequestContext) => void \| Promise<void>` | Before each HTTP request |
| `onResponse` | `(ctx: ResponseContext) => void \| Promise<void>` | After the final HTTP response of a call, once retries are done (2xx, 4xx, 5xx); not called on network errors |
| `onSuccess` | `(ctx: SuccessContext) => void \| Promise<void>` | After each 2xx response, with the parsed payload |
| `onRetry` | `(ctx: RetryContext) => void \| Promise<void>` | Before each retry attempt |
| `onError` | `(ctx: ErrorContext) => void \| Promise<void>` | On 4xx and 5xx (`APIError`) or a network error or timeout (`APIConnectionError`) |

For one call, the hooks run in this order: `onRequest`, then `onRetry` and `onRequest` again for each retry, then `onResponse`, then `onSuccess` or `onError`. The client awaits every hook that returns a `Promise`.

## Retries

By default the SDK retries a request up to 2 times on a 408, a 429, a 500, a 502, a 503, a 504, a timeout or a network error. The first retry waits 375 to 500 ms and each later one twice as long, up to 8 s. The initial interval is 500 ms, the exponent is 2, and a jitter shortens each delay to 75% to 100% of it.

* A 409 is retried only when the API sends `x-should-retry: true`. The API does so when the first attempt of a send with an idempotency key is still in progress, and the retry gets the stored response of that attempt. Any other conflict, such as a contact that already exists, does not resolve on its own.
* A `POST` without an `Idempotency-Key` is retried only on a 429, so a retry cannot create a duplicate. The eight sending methods listed in the README's [per-request options](https://github.com/nuntly/nuntly-sdk-typescript#per-request-options-and-timeouts) generate a key when you pass none.
* The SDK never retries a request you abort through `signal`, including with `AbortSignal.timeout(...)`, or an error thrown by a hook.
* A server-sent `x-should-retry: true` or `false` overrides these rules. A `Retry-After` header (seconds, decimals allowed, or an HTTP date) on any response replaces the backoff delay. Above 60 s the SDK does not wait: it throws the error for that response at once, with `RateLimitError.retryAfter` set on a 429.
* With `retry: 'none'`, a per-request `maxRetries` does not turn retries back on.

```ts theme={null}
// Disable retries
const noRetries = new Nuntly({
  apiKey: process.env.NUNTLY_API_KEY,
  retry: 'none',
});

// Custom backoff
const customBackoff = new Nuntly({
  apiKey: process.env.NUNTLY_API_KEY,
  maxRetries: 5,
  retry: {
    strategy: 'backoff',
    backoff: {
      initialInterval: 200, // ms
      maxInterval: 30000, // ms
      exponent: 1.5,
    },
  },
});

// Per-request override
await customBackoff.emails.send(payload, { maxRetries: 0 });
```

## Safe mode for one call

`createSafeNuntly` makes every call return `{ data, error }`. To get that shape for some calls only, wrap an existing client with `safe()`:

```ts theme={null}
import { Nuntly, safe } from '@nuntly/sdk';

const nuntly = new Nuntly({ apiKey: process.env.NUNTLY_API_KEY });
const { data, error } = await safe(nuntly).emails.retrieve('em_01ka8k8s80gvx9604cn9am5st4');
```

## Response headers without the body

`.asResponse()` resolves to the raw `Response` as soon as the headers arrive, without reading the body. Use it for streaming or to read headers only. It does not throw on a 4xx or 5xx, so check `response.status`:

```ts theme={null}
const response = await nuntly.emails.retrieve('em_01ka8k8s80gvx9604cn9am5st4').asResponse();
console.log(response.status, response.headers.get('x-request-id'));
```

## Abort a request

```ts theme={null}
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

await nuntly.emails.list(undefined, { signal: controller.signal });
```

## Debug logging

`debug: true` logs each request and response:

```ts theme={null}
const nuntly = new Nuntly({
  apiKey: process.env.NUNTLY_API_KEY,
  debug: true,
});
// -> POST https://api.nuntly.com/emails
// <- 202 POST /emails [req_abc123]
```

## Custom logger

`logger` accepts any logger compatible with `console`, such as pino or winston:

```ts theme={null}
import { Nuntly } from '@nuntly/sdk';
import pino from 'pino';

const nuntly = new Nuntly({
  apiKey: process.env.NUNTLY_API_KEY,
  logger: pino({ level: 'debug' }),
});
```

## Custom fetch

`fetch` replaces `globalThis.fetch` with any compatible function, for instrumentation, custom retry or another HTTP client. The SDK calls it with a `Request` as its first argument:

```ts theme={null}
import { Nuntly } from '@nuntly/sdk';

const nuntly = new Nuntly({
  apiKey: process.env.NUNTLY_API_KEY,
  fetch: async (input, init) => {
    const startedAt = Date.now();
    const response = await globalThis.fetch(input, init);
    console.log(`${response.status} ${response.url} (${Date.now() - startedAt}ms)`);
    return response;
  },
});
```

## HTTP and HTTPS proxies

On Node.js, pass an `undici` `ProxyAgent` as the `dispatcher` of the global `fetch` to send requests through a corporate or debugging proxy:

```ts theme={null}
import { Nuntly } from '@nuntly/sdk';
import { ProxyAgent } from 'undici';

const proxy = new ProxyAgent({
  uri: 'http://corporate-proxy:8080',
  token: `Basic ${process.env.PROXY_TOKEN}`, // only when the proxy requires authentication
});

const nuntly = new Nuntly({
  apiKey: process.env.NUNTLY_API_KEY,
  // `dispatcher` is a Node.js extension that the standard RequestInit type does not declare.
  fetch: (input, init) => fetch(input, { ...init, dispatcher: proxy } as RequestInit),
});
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.