diff --git a/readme.md b/readme.md index 0b9abc73..2248582b 100644 --- a/readme.md +++ b/readme.md @@ -134,6 +134,27 @@ console.log(`Email ${data.id} with a React template has been sent`); >}); >``` +## Request options + +`timeoutMs` and `retries` can be set as defaults for all requests, or overridden per call: + +```ts +const resend = new Resend('re_xxx', { + timeoutMs: 10_000, // abort each request attempt after 10s + retries: 3, // retry 429s, 5xx, and network errors +}); + +// override for a single request +await resend.emails.send({ ... }, { timeoutMs: 30_000, retries: 1 }); +``` + +When retrying, a `Retry-After` response header is honored; otherwise exponential backoff is applied. Timeouts and aborts are not retried. To cancel a request manually, pass an `AbortSignal`: + +```ts +const controller = new AbortController(); +await resend.emails.send({ ... }, { signal: controller.signal }); +``` + ## License MIT License diff --git a/src/api-keys/api-keys.ts b/src/api-keys/api-keys.ts index 6e3af13b..8ed03c42 100644 --- a/src/api-keys/api-keys.ts +++ b/src/api-keys/api-keys.ts @@ -35,7 +35,10 @@ export class ApiKeys { async list(options: ListApiKeysOptions = {}): Promise { const url = buildPaginationUrl('/api-keys', options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/automation-runs/automation-runs.ts b/src/automation-runs/automation-runs.ts index 9a50413d..daa9f786 100644 --- a/src/automation-runs/automation-runs.ts +++ b/src/automation-runs/automation-runs.ts @@ -41,7 +41,10 @@ export class AutomationRuns { ? `/automations/${options.automationId}/runs?${qs}` : `/automations/${options.automationId}/runs`; - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } } diff --git a/src/automations/automations.ts b/src/automations/automations.ts index c6b3e411..d2892f4d 100644 --- a/src/automations/automations.ts +++ b/src/automations/automations.ts @@ -69,7 +69,10 @@ export class Automations { const qs = params.filter(Boolean).join('&'); const url = qs ? `/automations?${qs}` : '/automations'; - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/broadcasts/broadcasts.ts b/src/broadcasts/broadcasts.ts index 910dd718..8fe8bdca 100644 --- a/src/broadcasts/broadcasts.ts +++ b/src/broadcasts/broadcasts.ts @@ -81,7 +81,10 @@ export class Broadcasts { ): Promise { const url = buildPaginationUrl('/broadcasts', options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/common/interfaces/delete-option.interface.ts b/src/common/interfaces/delete-option.interface.ts index 4a7f3527..220c82d4 100644 --- a/src/common/interfaces/delete-option.interface.ts +++ b/src/common/interfaces/delete-option.interface.ts @@ -1,3 +1,5 @@ -export interface DeleteOptions { +import type { RequestOptions } from './request-options.interface'; + +export interface DeleteOptions extends RequestOptions { headers?: HeadersInit; } diff --git a/src/common/interfaces/get-option.interface.ts b/src/common/interfaces/get-option.interface.ts index 78814b2f..06a624b3 100644 --- a/src/common/interfaces/get-option.interface.ts +++ b/src/common/interfaces/get-option.interface.ts @@ -1,4 +1,6 @@ -export interface GetOptions { +import type { RequestOptions } from './request-options.interface'; + +export interface GetOptions extends RequestOptions { query?: Record; headers?: HeadersInit; } diff --git a/src/common/interfaces/index.ts b/src/common/interfaces/index.ts index facdaf90..fe23fa13 100644 --- a/src/common/interfaces/index.ts +++ b/src/common/interfaces/index.ts @@ -8,4 +8,5 @@ export * from './pagination-options.interface'; export * from './patch-option.interface'; export * from './post-option.interface'; export * from './put-option.interface'; +export * from './request-options.interface'; export * from './require-at-least-one'; diff --git a/src/common/interfaces/pagination-options.interface.ts b/src/common/interfaces/pagination-options.interface.ts index 4d412380..d2043682 100644 --- a/src/common/interfaces/pagination-options.interface.ts +++ b/src/common/interfaces/pagination-options.interface.ts @@ -1,3 +1,5 @@ +import type { RequestOptions } from './request-options.interface'; + // Pagination options using cursor-based approach export type PaginationOptions = { /** @@ -19,7 +21,8 @@ export type PaginationOptions = { before?: string; after?: never; } -); +) & + RequestOptions; export type PaginatedData = { object: 'list'; diff --git a/src/common/interfaces/patch-option.interface.ts b/src/common/interfaces/patch-option.interface.ts index 2a21c3db..6c81e501 100644 --- a/src/common/interfaces/patch-option.interface.ts +++ b/src/common/interfaces/patch-option.interface.ts @@ -1,4 +1,6 @@ -export interface PatchOptions { +import type { RequestOptions } from './request-options.interface'; + +export interface PatchOptions extends RequestOptions { query?: { [key: string]: unknown }; headers?: HeadersInit; } diff --git a/src/common/interfaces/post-option.interface.ts b/src/common/interfaces/post-option.interface.ts index e56e6762..35e3d92c 100644 --- a/src/common/interfaces/post-option.interface.ts +++ b/src/common/interfaces/post-option.interface.ts @@ -1,4 +1,6 @@ -export interface PostOptions { +import type { RequestOptions } from './request-options.interface'; + +export interface PostOptions extends RequestOptions { query?: { [key: string]: unknown }; headers?: HeadersInit; } diff --git a/src/common/interfaces/put-option.interface.ts b/src/common/interfaces/put-option.interface.ts index 506d348e..2482ac3d 100644 --- a/src/common/interfaces/put-option.interface.ts +++ b/src/common/interfaces/put-option.interface.ts @@ -1,4 +1,6 @@ -export interface PutOptions { +import type { RequestOptions } from './request-options.interface'; + +export interface PutOptions extends RequestOptions { query?: { [key: string]: unknown }; headers?: HeadersInit; } diff --git a/src/common/interfaces/request-options.interface.ts b/src/common/interfaces/request-options.interface.ts new file mode 100644 index 00000000..6be4e77d --- /dev/null +++ b/src/common/interfaces/request-options.interface.ts @@ -0,0 +1,12 @@ +export interface RequestOptions { + /** Optional AbortSignal to cancel the request (also stops any pending retries). */ + signal?: AbortSignal; + /** Timeout in milliseconds per request attempt. Aborts the attempt when exceeded. */ + timeoutMs?: number; + /** + * Maximum number of retries for retryable failures (HTTP 429, 5xx, and network errors). + * A Retry-After header is honored when present, otherwise exponential backoff is used. + * Timeouts and aborts are not retried. + */ + retries?: number; +} diff --git a/src/contact-properties/contact-properties.ts b/src/contact-properties/contact-properties.ts index 2c56e051..335fc6b8 100644 --- a/src/contact-properties/contact-properties.ts +++ b/src/contact-properties/contact-properties.ts @@ -48,7 +48,7 @@ export class ContactProperties { const url = buildPaginationUrl('/contact-properties', options); const response = - await this.resend.get(url); + await this.resend.get(url, options); if (response.data) { return { diff --git a/src/contacts/contacts.ts b/src/contacts/contacts.ts index 88824445..9564e24d 100644 --- a/src/contacts/contacts.ts +++ b/src/contacts/contacts.ts @@ -105,12 +105,18 @@ export class Contacts { const segmentId = options.segmentId ?? options.audienceId; if (!segmentId) { const url = buildPaginationUrl('/contacts', options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } const url = buildPaginationUrl(`/segments/${segmentId}/contacts`, options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/contacts/imports/contact-imports.ts b/src/contacts/imports/contact-imports.ts index 8559e8ac..a1c50f69 100644 --- a/src/contacts/imports/contact-imports.ts +++ b/src/contacts/imports/contact-imports.ts @@ -49,7 +49,7 @@ export class ContactImports { ? `/contacts/imports?${queryString}` : '/contacts/imports'; - return this.resend.get(url); + return this.resend.get(url, options); } async get(id: string): Promise { diff --git a/src/contacts/segments/contact-segments.ts b/src/contacts/segments/contact-segments.ts index 338e4da4..be72a85d 100644 --- a/src/contacts/segments/contact-segments.ts +++ b/src/contacts/segments/contact-segments.ts @@ -37,7 +37,10 @@ export class ContactSegments { const identifier = options.email ? options.email : options.contactId; const url = buildPaginationUrl(`/contacts/${identifier}/segments`, options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/contacts/topics/contact-topics.ts b/src/contacts/topics/contact-topics.ts index 8ff1919e..230b4968 100644 --- a/src/contacts/topics/contact-topics.ts +++ b/src/contacts/topics/contact-topics.ts @@ -54,6 +54,6 @@ export class ContactTopics { const identifier = options.email ? options.email : options.id; const url = buildPaginationUrl(`/contacts/${identifier}/topics`, options); - return this.resend.get(url); + return this.resend.get(url, options); } } diff --git a/src/domains/domains.ts b/src/domains/domains.ts index fddd3da8..e51d1b3d 100644 --- a/src/domains/domains.ts +++ b/src/domains/domains.ts @@ -53,7 +53,10 @@ export class Domains { async list(options: ListDomainsOptions = {}): Promise { const url = buildPaginationUrl('/domains', options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/emails/attachments/attachments.ts b/src/emails/attachments/attachments.ts index 86ee77b0..fb2cbf58 100644 --- a/src/emails/attachments/attachments.ts +++ b/src/emails/attachments/attachments.ts @@ -29,7 +29,10 @@ export class Attachments { const url = buildPaginationUrl(`/emails/${emailId}/attachments`, options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/emails/emails.ts b/src/emails/emails.ts index 06640262..133cd2f8 100644 --- a/src/emails/emails.ts +++ b/src/emails/emails.ts @@ -75,7 +75,7 @@ export class Emails { async list(options: ListEmailsOptions = {}): Promise { const url = buildPaginationUrl('/emails', options); - const data = await this.resend.get(url); + const data = await this.resend.get(url, options); return data; } diff --git a/src/emails/receiving/attachments/attachments.ts b/src/emails/receiving/attachments/attachments.ts index 563d4f02..3d09f432 100644 --- a/src/emails/receiving/attachments/attachments.ts +++ b/src/emails/receiving/attachments/attachments.ts @@ -32,7 +32,10 @@ export class Attachments { options, ); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/emails/receiving/receiving.ts b/src/emails/receiving/receiving.ts index 684e1e4a..1f4d045a 100644 --- a/src/emails/receiving/receiving.ts +++ b/src/emails/receiving/receiving.ts @@ -51,7 +51,10 @@ export class Receiving { ): Promise { const url = buildPaginationUrl('/emails/receiving', options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/events/events.ts b/src/events/events.ts index daca6032..74765384 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -60,7 +60,7 @@ export class Events { async list(options: ListEventsOptions = {}): Promise { const url = buildPaginationUrl('/events', options); - const data = await this.resend.get(url); + const data = await this.resend.get(url, options); return data; } diff --git a/src/logs/logs.ts b/src/logs/logs.ts index 8e853ab6..45a2d931 100644 --- a/src/logs/logs.ts +++ b/src/logs/logs.ts @@ -15,7 +15,7 @@ export class Logs { async list(options: ListLogsOptions = {}): Promise { const url = buildPaginationUrl('/logs', options); - const data = await this.resend.get(url); + const data = await this.resend.get(url, options); return data; } diff --git a/src/oauth-grants/oauth-grants.ts b/src/oauth-grants/oauth-grants.ts index 7d981981..710f6be6 100644 --- a/src/oauth-grants/oauth-grants.ts +++ b/src/oauth-grants/oauth-grants.ts @@ -18,7 +18,10 @@ export class OAuthGrants { ): Promise { const url = buildPaginationUrl('/oauth/grants', options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/resend.spec.ts b/src/resend.spec.ts index 7935b658..385bbc0f 100644 --- a/src/resend.spec.ts +++ b/src/resend.spec.ts @@ -1,6 +1,9 @@ import createFetchMock from 'vitest-fetch-mock'; import { Resend } from './resend'; -import { mockSuccessResponse } from './test-utils/mock-fetch'; +import { + mockErrorResponse, + mockSuccessResponse, +} from './test-utils/mock-fetch'; const fetchMocker = createFetchMock(vi); fetchMocker.enableMocks(); @@ -132,4 +135,170 @@ describe('Resend', () => { expect(headers.get('User-Agent')).toBe(customUserAgent); }); }); + + describe('timeout', () => { + it('aborts the request when the configured timeoutMs is exceeded', async () => { + const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop', { + timeoutMs: 5, + }); + mockNeverResolvingFetch(); + + const result = await resend.apiKeys.list(); + + expect(result.error?.message).toBe( + 'Unable to fetch data. The request could not be resolved.', + ); + const init = fetchMock.mock.calls[0][1]; + expect((init as RequestInit).signal?.aborted).toBe(true); + }); + + it('cancels the request when the provided signal aborts', async () => { + const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop'); + const controller = new AbortController(); + const fetchStub = vi.fn((_url: string, init?: RequestInit) => { + return new Promise((_resolve, reject) => { + if (!init?.signal) { + reject(new Error('No AbortSignal passed to fetch')); + return; + } + if (init.signal.aborted) { + reject( + new DOMException('The operation was aborted.', 'AbortError'), + ); + return; + } + init.signal.addEventListener( + 'abort', + () => + reject( + new DOMException('The operation was aborted.', 'AbortError'), + ), + { once: true }, + ); + }); + }); + vi.stubGlobal('fetch', fetchStub); + + try { + const promise = resend.apiKeys.list({ signal: controller.signal }); + controller.abort(); + const result = await promise; + + expect(result.error?.message).toBe( + 'Unable to fetch data. The request could not be resolved.', + ); + expect( + (fetchStub.mock.calls[0][1] as RequestInit).signal?.aborted, + ).toBe(true); + } finally { + vi.unstubAllGlobals(); + } + }); + }); + + describe('retries', () => { + it('retries on HTTP 429 and honors the Retry-After header', async () => { + const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop', { + retries: 2, + }); + fetchMock.mockResponses( + [ + '{}', + { + status: 429, + headers: { 'content-type': 'application/json', 'retry-after': '0' }, + }, + ], + [ + '{"id": "key-123"}', + { status: 200, headers: { 'content-type': 'application/json' } }, + ], + ); + + const result = await resend.apiKeys.list(); + + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(result.data).toEqual({ id: 'key-123' }); + }); + + it('retries on HTTP 500', async () => { + const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop', { + retries: 1, + }); + fetchMock.mockResponses( + [ + '{}', + { + status: 500, + headers: { 'content-type': 'application/json', 'retry-after': '0' }, + }, + ], + [ + '{"id": "key-123"}', + { status: 200, headers: { 'content-type': 'application/json' } }, + ], + ); + + const result = await resend.apiKeys.list(); + + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(result.data).toEqual({ id: 'key-123' }); + }); + + it('does not retry on non-retryable status codes', async () => { + const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop', { + retries: 2, + }); + mockErrorResponse({ name: 'invalid_parameter', message: 'nope' }); + + const result = await resend.apiKeys.list(); + + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(result.error).toEqual({ + name: 'invalid_parameter', + message: 'nope', + }); + }); + + it('per-request retries override the constructor default', async () => { + const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop', { + retries: 0, + }); + fetchMock.mockResponses( + [ + '{}', + { + status: 429, + headers: { 'content-type': 'application/json', 'retry-after': '0' }, + }, + ], + [ + '{"id": "key-123"}', + { status: 200, headers: { 'content-type': 'application/json' } }, + ], + ); + + const result = await resend.apiKeys.list({ retries: 1 }); + + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(result.data).toEqual({ id: 'key-123' }); + }); + }); }); + +function mockNeverResolvingFetch() { + fetchMock.mockOnce((request) => { + return new Promise((_resolve, reject) => { + if (request.signal.aborted) { + reject(new DOMException('The operation was aborted.', 'AbortError')); + return; + } + request.signal.addEventListener( + 'abort', + () => + reject(new DOMException('The operation was aborted.', 'AbortError')), + { once: true }, + ); + }); + }); +} diff --git a/src/resend.ts b/src/resend.ts index fe4f5e0e..03ff69e5 100644 --- a/src/resend.ts +++ b/src/resend.ts @@ -8,6 +8,7 @@ import type { GetOptions, PostOptions, PutOptions, + RequestOptions, } from './common/interfaces'; import type { IdempotentRequest } from './common/interfaces/idempotent-request.interface'; import type { PatchOptions } from './common/interfaces/patch-option.interface'; @@ -16,7 +17,7 @@ import { Contacts } from './contacts/contacts'; import { Domains } from './domains/domains'; import { Emails } from './emails/emails'; import { Events } from './events/events'; -import type { ErrorResponse, Response } from './interfaces'; +import type { ErrorResponse, Response as ResendResponse } from './interfaces'; import { Logs } from './logs/logs'; import { OAuthGrants } from './oauth-grants/oauth-grants'; import { Segments } from './segments/segments'; @@ -43,11 +44,37 @@ function getDefaultUserAgent(): string { export interface ResendOptions { baseUrl?: string; userAgent?: string; + /** Default timeout in milliseconds per request attempt for every request. */ + timeoutMs?: number; + /** Default maximum number of retries for retryable failures. */ + retries?: number; } +function parseRetryAfter(value: string | null): number | undefined { + if (!value) { + return undefined; + } + + const seconds = Number(value); + if (!Number.isNaN(seconds)) { + return seconds * 1000; + } + + const date = Date.parse(value); + return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now()); +} + +type RequestAttempt = { + response: ResendResponse; + retryable: boolean; + retryAfterMs?: number; +}; + export class Resend { readonly baseUrl: string; readonly userAgent: string; + readonly timeoutMs?: number; + readonly retries?: number; private readonly headers: Headers; readonly segments = new Segments(this); @@ -89,6 +116,8 @@ export class Resend { this.baseUrl = options?.baseUrl ?? getDefaultBaseUrl(); this.userAgent = options?.userAgent ?? getDefaultUserAgent(); + this.timeoutMs = options?.timeoutMs; + this.retries = options?.retries; this.headers = new Headers({ Authorization: `Bearer ${this.key}`, @@ -111,73 +140,91 @@ export class Resend { } } - async fetchRequest(path: string, options = {}): Promise> { - try { - const response = await fetch(`${this.baseUrl}${path}`, options); - - if (!response.ok) { - try { - const rawError = await response.text(); - const parsedError = JSON.parse(rawError); - - this.logError(parsedError, path, response.status); - - return { - data: null, - error: parsedError, - headers: Object.fromEntries(response.headers.entries()), - }; - } catch (err) { - if (err instanceof SyntaxError) { - const error: ErrorResponse = { - name: 'application_error', - statusCode: response.status, - message: - 'Internal server error. We are unable to process your request right now, please try again later.', - }; - - this.logError(error, path, response.status); - - return { - data: null, - error, - headers: Object.fromEntries(response.headers.entries()), - }; + async fetchRequest( + path: string, + options: RequestOptions & RequestInit = {}, + ): Promise> { + const { signal, timeoutMs, retries, ...requestInit } = options; + const maxRetries = retries ?? this.retries ?? 0; + const timeout = timeoutMs ?? this.timeoutMs; + + for (let attempt = 0; ; attempt++) { + let controller: AbortController | undefined; + let timer: ReturnType | undefined; + let onAbort: (() => void) | undefined; + let effectiveSignal = signal; + + if (timeout) { + controller = new AbortController(); + effectiveSignal = controller.signal; + + if (signal) { + if (signal.aborted) { + controller.abort(); + } else { + onAbort = () => controller?.abort(); + signal.addEventListener('abort', onAbort, { once: true }); } + } + + timer = setTimeout(() => controller?.abort(), timeout); + } - const error: ErrorResponse = { - message: response.statusText, - statusCode: response.status, - name: 'application_error', - }; + let result: RequestAttempt; + try { + result = await this.performRequest(path, { + ...requestInit, + signal: effectiveSignal, + }); + } finally { + if (timer) { + clearTimeout(timer); + } + if (onAbort) { + signal?.removeEventListener('abort', onAbort); + } + } - if (err instanceof Error) { - const errorWithMessage = { ...error, message: err.message }; + const retryable = + attempt < maxRetries && !effectiveSignal?.aborted && result.retryable; - this.logError(errorWithMessage, path, response.status); + if (!retryable) { + return result.response; + } - return { - data: null, - error: errorWithMessage, - headers: Object.fromEntries(response.headers.entries()), - }; - } + const backoffMs = + result.retryAfterMs ?? Math.min(500 * 2 ** attempt, 10_000); + await new Promise((resolve) => + setTimeout(resolve, backoffMs + Math.random() * 250), + ); + } + } - this.logError(error, path, response.status); + private async performRequest( + path: string, + init: RequestInit, + ): Promise> { + try { + const response = await fetch(`${this.baseUrl}${path}`, init); - return { - data: null, - error, - headers: Object.fromEntries(response.headers.entries()), - }; - } + if (!response.ok) { + return { + response: await this.buildErrorResponse(response, path), + retryable: + (response.status === 429 || response.status >= 500) && + !init.signal?.aborted, + retryAfterMs: parseRetryAfter(response.headers.get('retry-after')), + }; } const data = await response.json(); return { - data, - error: null, - headers: Object.fromEntries(response.headers.entries()), + response: { + data, + error: null, + headers: Object.fromEntries(response.headers.entries()), + }, + retryable: false, }; } catch { const error: ErrorResponse = { @@ -188,10 +235,74 @@ export class Resend { this.logError(error, path); + return { + response: { + data: null, + error, + headers: null, + }, + retryable: !init.signal?.aborted, + }; + } + } + + private async buildErrorResponse( + response: Response, + path: string, + ): Promise> { + try { + const rawError = await response.text(); + const parsedError = JSON.parse(rawError); + + this.logError(parsedError, path, response.status); + + return { + data: null, + error: parsedError, + headers: Object.fromEntries(response.headers.entries()), + }; + } catch (err) { + if (err instanceof SyntaxError) { + const error: ErrorResponse = { + name: 'application_error', + statusCode: response.status, + message: + 'Internal server error. We are unable to process your request right now, please try again later.', + }; + + this.logError(error, path, response.status); + + return { + data: null, + error, + headers: Object.fromEntries(response.headers.entries()), + }; + } + + const error: ErrorResponse = { + message: response.statusText, + statusCode: response.status, + name: 'application_error', + }; + + if (err instanceof Error) { + const errorWithMessage = { ...error, message: err.message }; + + this.logError(errorWithMessage, path, response.status); + + return { + data: null, + error: errorWithMessage, + headers: Object.fromEntries(response.headers.entries()), + }; + } + + this.logError(error, path, response.status); + return { data: null, error, - headers: null, + headers: Object.fromEntries(response.headers.entries()), }; } } diff --git a/src/segments/segments.ts b/src/segments/segments.ts index 59144bbb..047b4dc7 100644 --- a/src/segments/segments.ts +++ b/src/segments/segments.ts @@ -38,7 +38,10 @@ export class Segments { async list(options: ListSegmentsOptions = {}): Promise { const url = buildPaginationUrl('/segments', options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; } diff --git a/src/suppressions/suppressions.ts b/src/suppressions/suppressions.ts index aa697cbe..0a4509cd 100644 --- a/src/suppressions/suppressions.ts +++ b/src/suppressions/suppressions.ts @@ -50,7 +50,7 @@ export class Suppressions { const queryString = buildSuppressionsQuery(options); const url = queryString ? `/suppressions?${queryString}` : '/suppressions'; - return this.resend.get(url); + return this.resend.get(url, options); } async get(idOrEmail: string): Promise { diff --git a/src/templates/templates.ts b/src/templates/templates.ts index 2e232bdd..271a733d 100644 --- a/src/templates/templates.ts +++ b/src/templates/templates.ts @@ -80,6 +80,7 @@ export class Templates { async list(options: PaginationOptions = {}): Promise { return this.resend.get( `/templates${getPaginationQueryProperties(options)}`, + options, ); } diff --git a/src/webhooks/webhooks.ts b/src/webhooks/webhooks.ts index 22105103..c48aa3a0 100644 --- a/src/webhooks/webhooks.ts +++ b/src/webhooks/webhooks.ts @@ -65,7 +65,10 @@ export class Webhooks { async list(options: ListWebhooksOptions = {}): Promise { const url = buildPaginationUrl('/webhooks', options); - const data = await this.resend.get(url); + const data = await this.resend.get( + url, + options, + ); return data; }