@nateq/sdk 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["#apiKey"],"sources":["../src/errors.ts","../src/emails.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["/**\n * Error types raised by the Nateq SDK.\n *\n * Every error extends {@link NateqError}, so a single `catch (err) { if (err\n * instanceof NateqError) ... }` covers the whole surface. No error ever carries\n * the API key: see `redact` in `http.ts`.\n */\n\n/** Machine-readable code returned by the API, when it sends one. */\nexport type NateqErrorCode =\n | \"MISSING_API_KEY\"\n | \"INVALID_AUTH_FORMAT\"\n | \"INVALID_API_KEY_FORMAT\"\n | \"INVALID_API_KEY\"\n | \"AUTHENTICATION_FAILED\"\n | \"ENDPOINT_NOT_PUBLIC\"\n | \"INSUFFICIENT_SCOPE\"\n | \"INSUFFICIENT_PERMISSION\"\n | \"IP_NOT_ALLOWED\"\n | \"RATE_LIMIT_EXCEEDED\"\n | (string & {});\n\nexport class NateqError extends Error {\n /** HTTP status, when the failure came from a response. */\n readonly status?: number | undefined;\n /** API error code, when the response carried one. */\n readonly code?: NateqErrorCode | undefined;\n /** Extra context from the API (validation details, scope lists, ...). */\n readonly details?: unknown;\n /** Value of the `X-Request-Id` response header, useful for support tickets. */\n readonly requestId?: string | undefined;\n\n constructor(\n message: string,\n opts: {\n status?: number | undefined;\n code?: NateqErrorCode | undefined;\n details?: unknown;\n requestId?: string | undefined;\n cause?: unknown;\n } = {},\n ) {\n super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);\n this.name = new.target.name;\n this.status = opts.status;\n this.code = opts.code;\n this.details = opts.details;\n this.requestId = opts.requestId;\n Error.captureStackTrace?.(this, new.target);\n }\n}\n\n/** The key is missing, malformed, revoked, or unknown (401). */\nexport class NateqAuthenticationError extends NateqError {}\n\n/**\n * The key is valid but not allowed to do this (403).\n *\n * Common causes: the key lacks the `emails:send` scope, the endpoint is not\n * exposed to API keys, or the caller's IP is outside the key's allow-list.\n */\nexport class NateqPermissionError extends NateqError {}\n\n/** The request body failed validation (400/422). */\nexport class NateqValidationError extends NateqError {}\n\n/** The requested resource does not exist, or is not visible to this key (404). */\nexport class NateqNotFoundError extends NateqError {}\n\n/** A rate limit was hit (429). Nothing was sent — this is safe to retry. */\nexport class NateqRateLimitError extends NateqError {\n /** Seconds to wait before retrying, when the API supplies `Retry-After`. */\n readonly retryAfter?: number | undefined;\n\n constructor(\n message: string,\n opts: ConstructorParameters<typeof NateqError>[1] & { retryAfter?: number | undefined } = {},\n ) {\n super(message, opts);\n this.retryAfter = opts.retryAfter;\n }\n}\n\n/** The API failed to handle the request (5xx). */\nexport class NateqServerError extends NateqError {}\n\n/** The request never produced a response: DNS, TLS, socket, or offline. */\nexport class NateqConnectionError extends NateqError {}\n\n/** The request exceeded the configured timeout, or was aborted by the caller. */\nexport class NateqTimeoutError extends NateqError {}\n\n/** Bad SDK usage — thrown before any network call (never carries a status). */\nexport class NateqConfigurationError extends NateqError {}\n","import { NateqValidationError } from \"./errors.js\";\nimport type { Transport } from \"./http.js\";\nimport type {\n ListEmailsParams,\n ListEmailsResult,\n OutboundEmail,\n SendEmailParams,\n SendEmailResult,\n} from \"./types.js\";\n\n/** Outbound email operations. Reachable at `client.emails`. */\nexport class Emails {\n constructor(private readonly transport: Transport) {}\n\n /**\n * Sends an email, returning once the API has accepted it. Delivery itself is\n * asynchronous — poll {@link get} or subscribe to a webhook for the outcome.\n *\n * Requires an API key with the `emails:send` scope.\n *\n * A failed send is **not** replayed automatically unless the API rejected it\n * outright (429), because a request that fails after reaching the server may\n * already have sent the mail. On a timeout or 5xx, look the email up with\n * {@link list} before retrying, or you risk sending twice.\n *\n * @throws {NateqValidationError} if the params are unusable, before any call.\n * @throws {NateqPermissionError} if the key lacks the `emails:send` scope.\n */\n async send(params: SendEmailParams, opts: { signal?: AbortSignal } = {}): Promise<SendEmailResult> {\n // Validate locally what the API would reject anyway, so the common mistakes\n // surface as a typed error instead of an opaque 400.\n if (!params.toEmails?.length) {\n throw new NateqValidationError(\"`toEmails` must contain at least one recipient.\");\n }\n if (!params.subject?.trim()) {\n throw new NateqValidationError(\"`subject` is required.\");\n }\n if (!params.htmlBody?.trim() && !params.plainTextBody?.trim()) {\n throw new NateqValidationError(\"Provide `htmlBody`, `plainTextBody`, or both.\");\n }\n\n return this.transport.request<SendEmailResult>({\n method: \"POST\",\n path: \"/outbound-emails\",\n body: params,\n signal: opts.signal,\n // Sending is not idempotent and the API has no idempotency key, so an\n // ambiguous failure must surface rather than silently double-send.\n retryOnAmbiguousFailure: false,\n });\n }\n\n /**\n * Fetches a single sent email by id, including delivery status.\n *\n * Requires an API key with the `emails:read` scope.\n */\n async get(id: string, opts: { signal?: AbortSignal } = {}): Promise<OutboundEmail> {\n if (!id?.trim()) throw new NateqValidationError(\"`id` is required.\");\n\n return this.transport.request<OutboundEmail>({\n method: \"GET\",\n path: `/outbound-emails/${encodeURIComponent(id)}`,\n signal: opts.signal,\n retryOnAmbiguousFailure: true,\n });\n }\n\n /**\n * Lists sent emails, newest first. `limit` defaults to 50 and is capped at 100.\n *\n * Requires an API key with the `emails:read` scope.\n */\n async list(\n params: ListEmailsParams = {},\n opts: { signal?: AbortSignal } = {},\n ): Promise<ListEmailsResult> {\n return this.transport.request<ListEmailsResult>({\n method: \"GET\",\n path: \"/outbound-emails\",\n query: params as Record<string, unknown>,\n signal: opts.signal,\n retryOnAmbiguousFailure: true,\n });\n }\n}\n","import {\n NateqAuthenticationError,\n NateqConnectionError,\n NateqError,\n NateqNotFoundError,\n NateqPermissionError,\n NateqRateLimitError,\n NateqServerError,\n NateqTimeoutError,\n NateqValidationError,\n type NateqErrorCode,\n} from \"./errors.js\";\n\n/** Subset of `fetch` the SDK relies on, so callers can inject their own. */\nexport type FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\nexport interface TransportOptions {\n apiKey: string;\n baseUrl: string;\n timeout: number;\n maxRetries: number;\n fetch: FetchLike;\n userAgent: string;\n}\n\nexport interface RequestOptions {\n method: \"GET\" | \"POST\";\n path: string;\n query?: Record<string, unknown> | undefined;\n body?: unknown;\n signal?: AbortSignal | undefined;\n /**\n * Whether this request may be replayed after an ambiguous failure (network\n * error, timeout, 5xx) — i.e. a failure where the server may already have\n * acted. Only safe for requests with no side effects.\n *\n * A 429 is retried regardless: the API rejects rate-limited sends during\n * validation, before any mail leaves, so no duplicate can result.\n */\n retryOnAmbiguousFailure: boolean;\n}\n\n/**\n * The two error envelopes the API emits. Auth middleware returns the standard\n * `{success,error:{code,message,details}}` envelope; the outbound-email handler\n * returns a flat `{error,details}`. Both are parsed into one error type.\n */\ninterface EnvelopeError {\n success?: boolean;\n error?: { code?: string; message?: string; details?: unknown } | string;\n details?: unknown;\n}\n\n/**\n * Removes the API key from a string. Applied to every message the SDK raises so\n * a leaked stack trace or log line can never reveal the credential.\n */\nfunction redact(text: string, apiKey: string): string {\n return apiKey ? text.split(apiKey).join(\"[REDACTED]\") : text;\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === \"AbortError\";\n}\n\nfunction parseRetryAfter(headers: Headers): number | undefined {\n const raw = headers.get(\"retry-after\");\n if (!raw) return undefined;\n const seconds = Number(raw);\n if (Number.isFinite(seconds) && seconds >= 0) return seconds;\n const date = Date.parse(raw);\n if (Number.isNaN(date)) return undefined;\n return Math.max(0, (date - Date.now()) / 1000);\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: Record<string, unknown>): string {\n const url = new URL(baseUrl.replace(/\\/+$/, \"\") + path);\n for (const [key, value] of Object.entries(query ?? {})) {\n if (value === undefined || value === null) continue;\n url.searchParams.set(key, String(value));\n }\n return url.toString();\n}\n\n/** Maps a failed response onto the matching error class. */\nfunction toError(\n status: number,\n payload: EnvelopeError | undefined,\n headers: Headers,\n apiKey: string,\n): NateqError {\n let code: string | undefined;\n let message: string | undefined;\n let details: unknown;\n\n const err = payload?.error;\n if (typeof err === \"string\") {\n // Flat shape: { error: \"Failed to send email\", details: \"...\" }\n message = err;\n details = payload?.details;\n } else if (err && typeof err === \"object\") {\n // Envelope shape: { success: false, error: { code, message, details } }\n code = err.code;\n message = err.message;\n details = err.details;\n }\n\n const requestId = headers.get(\"x-request-id\") ?? undefined;\n const text = redact(message ?? `Request failed with status ${status}`, apiKey);\n const opts = {\n status,\n code: code as NateqErrorCode | undefined,\n details,\n requestId,\n };\n\n if (status === 401) return new NateqAuthenticationError(text, opts);\n if (status === 403) return new NateqPermissionError(text, opts);\n if (status === 404) return new NateqNotFoundError(text, opts);\n if (status === 400 || status === 422) return new NateqValidationError(text, opts);\n if (status === 429) {\n return new NateqRateLimitError(text, { ...opts, retryAfter: parseRetryAfter(headers) });\n }\n if (status >= 500) return new NateqServerError(text, opts);\n return new NateqError(text, opts);\n}\n\n/** Full-jitter exponential backoff, capped at 8s, honouring `Retry-After`. */\nfunction backoffDelay(attempt: number, retryAfter?: number): number {\n if (retryAfter !== undefined) return Math.min(retryAfter * 1000, 60_000);\n const ceiling = Math.min(500 * 2 ** attempt, 8_000);\n return Math.random() * ceiling;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new NateqTimeoutError(\"Request aborted by caller\"));\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(new NateqTimeoutError(\"Request aborted by caller\"));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nexport class Transport {\n constructor(private readonly options: TransportOptions) {}\n\n async request<T>(req: RequestOptions): Promise<T> {\n const { apiKey, baseUrl, timeout, maxRetries, fetch: doFetch, userAgent } = this.options;\n const url = buildUrl(baseUrl, req.path, req.query);\n\n let lastError: NateqError | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const retryAfter = lastError instanceof NateqRateLimitError ? lastError.retryAfter : undefined;\n await sleep(backoffDelay(attempt - 1, retryAfter), req.signal);\n }\n\n // Per-attempt timeout, combined with any caller-supplied signal.\n const timeoutSignal = AbortSignal.timeout(timeout);\n const signal = req.signal\n ? AbortSignal.any([req.signal, timeoutSignal])\n : timeoutSignal;\n\n let response: Response;\n try {\n response = await doFetch(url, {\n method: req.method,\n signal,\n headers: {\n // The key travels in the Authorization header only — never in the\n // URL, where it would land in server logs and browser history.\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n \"User-Agent\": userAgent,\n ...(req.body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n },\n ...(req.body !== undefined ? { body: JSON.stringify(req.body) } : {}),\n });\n } catch (err) {\n // The request produced no response. Whether the server acted is unknown.\n const caller = req.signal?.aborted === true;\n lastError = caller\n ? new NateqTimeoutError(\"Request aborted by caller\", { cause: err })\n : isAbortError(err) || (err as Error)?.name === \"TimeoutError\"\n ? new NateqTimeoutError(`Request timed out after ${timeout}ms`, { cause: err })\n : new NateqConnectionError(\n redact(`Could not reach the Nateq API: ${(err as Error)?.message ?? err}`, apiKey),\n { cause: err },\n );\n\n if (caller) throw lastError;\n if (req.retryOnAmbiguousFailure && attempt < maxRetries) continue;\n throw lastError;\n }\n\n if (response.ok) {\n if (response.status === 204) return undefined as T;\n return (await response.json()) as T;\n }\n\n let payload: EnvelopeError | undefined;\n try {\n payload = (await response.json()) as EnvelopeError;\n } catch {\n payload = undefined;\n }\n\n lastError = toError(response.status, payload, response.headers, apiKey);\n\n const retriable =\n lastError instanceof NateqRateLimitError ||\n (req.retryOnAmbiguousFailure &&\n (lastError instanceof NateqServerError || response.status === 408));\n\n if (retriable && attempt < maxRetries) continue;\n throw lastError;\n }\n\n /* istanbul ignore next — the loop always returns or throws. */\n throw lastError ?? new NateqError(\"Request failed\");\n }\n}\n","import { Emails } from \"./emails.js\";\nimport { NateqConfigurationError } from \"./errors.js\";\nimport { Transport, type FetchLike } from \"./http.js\";\n\nconst VERSION = \"0.1.0\";\n\n/** Recognised API-key prefixes, mirroring the API's own validation. */\nconst KEY_PREFIXES = [\"tg_live_\", \"tg_test_\"] as const;\nconst MIN_KEY_LENGTH = 50;\n\nexport interface NateqOptions {\n /**\n * Your Nateq API key. Defaults to `process.env.NATEQ_API_KEY`.\n *\n * Load this from the environment or a secret manager — never commit it, and\n * never ship it to a browser: the key carries your organization's full scope\n * grant and this SDK is server-side only.\n */\n apiKey?: string;\n /** API base URL. Defaults to `process.env.NATEQ_BASE_URL` or production. */\n baseUrl?: string;\n /** Per-request timeout in milliseconds. Defaults to 30000. */\n timeout?: number;\n /** Retries after a retriable failure. Defaults to 2. Set 0 to disable. */\n maxRetries?: number;\n /** Custom fetch, for tests or proxies. Defaults to the global `fetch`. */\n fetch?: FetchLike;\n}\n\nfunction resolveApiKey(explicit?: string): string {\n const key = explicit ?? process.env[\"NATEQ_API_KEY\"];\n\n if (!key) {\n throw new NateqConfigurationError(\n \"No API key provided. Pass `new Nateq({ apiKey })` or set the NATEQ_API_KEY environment variable.\",\n );\n }\n if (key !== key.trim()) {\n throw new NateqConfigurationError(\n \"The API key has leading or trailing whitespace, which usually means it was copied incorrectly.\",\n );\n }\n // Validate locally so an obviously bad key fails fast, without a round-trip\n // that would put the credential on the wire.\n if (!KEY_PREFIXES.some((p) => key.startsWith(p))) {\n throw new NateqConfigurationError(\n `Invalid API key format: expected it to start with ${KEY_PREFIXES.join(\" or \")}.`,\n );\n }\n if (key.length < MIN_KEY_LENGTH) {\n throw new NateqConfigurationError(\"Invalid API key format: the key is too short.\");\n }\n return key;\n}\n\nfunction resolveBaseUrl(explicit?: string): string {\n const raw = explicit ?? process.env[\"NATEQ_BASE_URL\"] ?? \"https://api.nateq.io/api/v1\";\n\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new NateqConfigurationError(`Invalid baseUrl: ${raw}`);\n }\n\n // Refuse to send the key over cleartext. Loopback is exempt so local\n // development against a plain-HTTP server still works.\n const isLoopback = [\"localhost\", \"127.0.0.1\", \"[::1]\", \"::1\"].includes(url.hostname);\n if (url.protocol !== \"https:\" && !isLoopback) {\n throw new NateqConfigurationError(\n `Refusing to send an API key over ${url.protocol}// to ${url.hostname}. Use https, or a localhost address for local development.`,\n );\n }\n return url.toString().replace(/\\/+$/, \"\");\n}\n\n/**\n * The Nateq API client.\n *\n * ```ts\n * import { Nateq } from \"@nateq/sdk\";\n *\n * const nateq = new Nateq(); // reads NATEQ_API_KEY\n *\n * await nateq.emails.send({\n * toEmails: [\"customer@example.com\"],\n * subject: \"Welcome aboard\",\n * htmlBody: \"<p>Thanks for signing up.</p>\",\n * });\n * ```\n *\n * The instance holds your API key, so it is never safe to serialise: `console.log`,\n * `JSON.stringify`, and util.inspect all render it with the key redacted.\n */\nexport class Nateq {\n /** Outbound email: send, get, list. */\n readonly emails: Emails;\n\n readonly baseUrl: string;\n\n /** Stored non-enumerably so the key cannot leak via property enumeration. */\n readonly #apiKey: string;\n\n constructor(options: NateqOptions = {}) {\n this.#apiKey = resolveApiKey(options.apiKey);\n this.baseUrl = resolveBaseUrl(options.baseUrl);\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new NateqConfigurationError(\n \"No global fetch available. Use Node 18+, or pass a fetch implementation via `new Nateq({ fetch })`.\",\n );\n }\n\n const transport = new Transport({\n apiKey: this.#apiKey,\n baseUrl: this.baseUrl,\n timeout: options.timeout ?? 30_000,\n maxRetries: options.maxRetries ?? 2,\n fetch: fetchImpl.bind(globalThis) as FetchLike,\n userAgent: `nateq-node/${VERSION} (node ${process.versions.node})`,\n });\n\n this.emails = new Emails(transport);\n }\n\n /** Last four characters of the key, for logging which key is in use. */\n get apiKeyLast4(): string {\n return this.#apiKey.slice(-4);\n }\n\n toJSON(): Record<string, unknown> {\n return { baseUrl: this.baseUrl, apiKey: `[REDACTED:...${this.apiKeyLast4}]` };\n }\n\n /** Keeps the key out of `console.log(client)` and util.inspect output. */\n [Symbol.for(\"nodejs.util.inspect.custom\")](): string {\n return `Nateq { baseUrl: '${this.baseUrl}', apiKey: '[REDACTED:...${this.apiKeyLast4}]' }`;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,278 @@
1
+ //#region src/http.d.ts
2
+ /** Subset of `fetch` the SDK relies on, so callers can inject their own. */
3
+ type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
4
+ interface TransportOptions {
5
+ apiKey: string;
6
+ baseUrl: string;
7
+ timeout: number;
8
+ maxRetries: number;
9
+ fetch: FetchLike;
10
+ userAgent: string;
11
+ }
12
+ interface RequestOptions {
13
+ method: "GET" | "POST";
14
+ path: string;
15
+ query?: Record<string, unknown> | undefined;
16
+ body?: unknown;
17
+ signal?: AbortSignal | undefined;
18
+ /**
19
+ * Whether this request may be replayed after an ambiguous failure (network
20
+ * error, timeout, 5xx) — i.e. a failure where the server may already have
21
+ * acted. Only safe for requests with no side effects.
22
+ *
23
+ * A 429 is retried regardless: the API rejects rate-limited sends during
24
+ * validation, before any mail leaves, so no duplicate can result.
25
+ */
26
+ retryOnAmbiguousFailure: boolean;
27
+ }
28
+ declare class Transport {
29
+ private readonly options;
30
+ constructor(options: TransportOptions);
31
+ request<T>(req: RequestOptions): Promise<T>;
32
+ }
33
+ //#endregion
34
+ //#region src/types.d.ts
35
+ /** Delivery status of an outbound email. */
36
+ type OutboundEmailStatus = "pending" | "sending" | "sent" | "delivered" | "bounced" | "failed" | "rejected" | "opened" | "clicked";
37
+ /**
38
+ * Body of a send request.
39
+ *
40
+ * At least one of `htmlBody` / `plainTextBody` must be present. When
41
+ * `emailAddressId` and `fromEmail` are both omitted, the organization's default
42
+ * verified address is used.
43
+ */
44
+ interface SendEmailParams {
45
+ /** Verified organization address to send from. Defaults to the org default. */
46
+ emailAddressId?: string;
47
+ /** Override the From address. Must be a verified organization address. */
48
+ fromEmail?: string;
49
+ fromName?: string;
50
+ /** One or more recipients. Required. */
51
+ toEmails: string[];
52
+ ccEmails?: string[];
53
+ bccEmails?: string[];
54
+ replyTo?: string;
55
+ /** Required, 1-998 characters. */
56
+ subject: string;
57
+ htmlBody?: string;
58
+ plainTextBody?: string;
59
+ /** `Message-ID` this email replies to (threading). */
60
+ inReplyTo?: string;
61
+ /** `References` header (threading). */
62
+ references?: string;
63
+ ticketId?: string;
64
+ conversationId?: string;
65
+ contactId?: string;
66
+ /** IDs of previously uploaded files to attach. */
67
+ attachmentIds?: string[];
68
+ /** Extra custom headers. */
69
+ headers?: Record<string, string>;
70
+ }
71
+ /** Result of a successful send. */
72
+ interface SendEmailResult {
73
+ id: string;
74
+ providerMessageId?: string;
75
+ status: OutboundEmailStatus;
76
+ createdAt: string;
77
+ }
78
+ /** A stored outbound email, as returned by `get` and `list`. */
79
+ interface OutboundEmail {
80
+ id: string;
81
+ organizationId: string;
82
+ emailAddressId?: string;
83
+ messageId?: string;
84
+ providerMessageId?: string;
85
+ fromEmail: string;
86
+ fromName?: string;
87
+ toEmails: string[];
88
+ ccEmails?: string[];
89
+ bccEmails?: string[];
90
+ subject?: string;
91
+ htmlBody?: string;
92
+ plainTextBody?: string;
93
+ status: OutboundEmailStatus;
94
+ ticketId?: string;
95
+ conversationId?: string;
96
+ contactId?: string;
97
+ sentByUserId?: string;
98
+ isAutomatic?: boolean;
99
+ deliveredAt?: string;
100
+ bouncedAt?: string;
101
+ openedAt?: string;
102
+ openCount?: number;
103
+ bounceReason?: string;
104
+ createdAt: string;
105
+ updatedAt: string;
106
+ [key: string]: unknown;
107
+ }
108
+ /** Filters for `emails.list`. */
109
+ interface ListEmailsParams {
110
+ emailAddressId?: string;
111
+ status?: OutboundEmailStatus;
112
+ fromEmail?: string;
113
+ /** Match against any recipient. */
114
+ toEmail?: string;
115
+ ticketId?: string;
116
+ conversationId?: string;
117
+ contactId?: string;
118
+ sentByUserId?: string;
119
+ isAutomatic?: boolean;
120
+ /** Defaults to 50 server-side; capped at 100. */
121
+ limit?: number;
122
+ offset?: number;
123
+ }
124
+ /** A page of outbound emails. */
125
+ interface ListEmailsResult {
126
+ emails: OutboundEmail[];
127
+ total: number;
128
+ limit: number;
129
+ offset: number;
130
+ }
131
+ //#endregion
132
+ //#region src/emails.d.ts
133
+ /** Outbound email operations. Reachable at `client.emails`. */
134
+ declare class Emails {
135
+ private readonly transport;
136
+ constructor(transport: Transport);
137
+ /**
138
+ * Sends an email, returning once the API has accepted it. Delivery itself is
139
+ * asynchronous — poll {@link get} or subscribe to a webhook for the outcome.
140
+ *
141
+ * Requires an API key with the `emails:send` scope.
142
+ *
143
+ * A failed send is **not** replayed automatically unless the API rejected it
144
+ * outright (429), because a request that fails after reaching the server may
145
+ * already have sent the mail. On a timeout or 5xx, look the email up with
146
+ * {@link list} before retrying, or you risk sending twice.
147
+ *
148
+ * @throws {NateqValidationError} if the params are unusable, before any call.
149
+ * @throws {NateqPermissionError} if the key lacks the `emails:send` scope.
150
+ */
151
+ send(params: SendEmailParams, opts?: {
152
+ signal?: AbortSignal;
153
+ }): Promise<SendEmailResult>;
154
+ /**
155
+ * Fetches a single sent email by id, including delivery status.
156
+ *
157
+ * Requires an API key with the `emails:read` scope.
158
+ */
159
+ get(id: string, opts?: {
160
+ signal?: AbortSignal;
161
+ }): Promise<OutboundEmail>;
162
+ /**
163
+ * Lists sent emails, newest first. `limit` defaults to 50 and is capped at 100.
164
+ *
165
+ * Requires an API key with the `emails:read` scope.
166
+ */
167
+ list(params?: ListEmailsParams, opts?: {
168
+ signal?: AbortSignal;
169
+ }): Promise<ListEmailsResult>;
170
+ }
171
+ //#endregion
172
+ //#region src/client.d.ts
173
+ interface NateqOptions {
174
+ /**
175
+ * Your Nateq API key. Defaults to `process.env.NATEQ_API_KEY`.
176
+ *
177
+ * Load this from the environment or a secret manager — never commit it, and
178
+ * never ship it to a browser: the key carries your organization's full scope
179
+ * grant and this SDK is server-side only.
180
+ */
181
+ apiKey?: string;
182
+ /** API base URL. Defaults to `process.env.NATEQ_BASE_URL` or production. */
183
+ baseUrl?: string;
184
+ /** Per-request timeout in milliseconds. Defaults to 30000. */
185
+ timeout?: number;
186
+ /** Retries after a retriable failure. Defaults to 2. Set 0 to disable. */
187
+ maxRetries?: number;
188
+ /** Custom fetch, for tests or proxies. Defaults to the global `fetch`. */
189
+ fetch?: FetchLike;
190
+ }
191
+ /**
192
+ * The Nateq API client.
193
+ *
194
+ * ```ts
195
+ * import { Nateq } from "@nateq/sdk";
196
+ *
197
+ * const nateq = new Nateq(); // reads NATEQ_API_KEY
198
+ *
199
+ * await nateq.emails.send({
200
+ * toEmails: ["customer@example.com"],
201
+ * subject: "Welcome aboard",
202
+ * htmlBody: "<p>Thanks for signing up.</p>",
203
+ * });
204
+ * ```
205
+ *
206
+ * The instance holds your API key, so it is never safe to serialise: `console.log`,
207
+ * `JSON.stringify`, and util.inspect all render it with the key redacted.
208
+ */
209
+ declare class Nateq {
210
+ #private;
211
+ /** Outbound email: send, get, list. */
212
+ readonly emails: Emails;
213
+ readonly baseUrl: string;
214
+ constructor(options?: NateqOptions);
215
+ /** Last four characters of the key, for logging which key is in use. */
216
+ get apiKeyLast4(): string;
217
+ toJSON(): Record<string, unknown>;
218
+ }
219
+ //#endregion
220
+ //#region src/errors.d.ts
221
+ /**
222
+ * Error types raised by the Nateq SDK.
223
+ *
224
+ * Every error extends {@link NateqError}, so a single `catch (err) { if (err
225
+ * instanceof NateqError) ... }` covers the whole surface. No error ever carries
226
+ * the API key: see `redact` in `http.ts`.
227
+ */
228
+ /** Machine-readable code returned by the API, when it sends one. */
229
+ type NateqErrorCode = "MISSING_API_KEY" | "INVALID_AUTH_FORMAT" | "INVALID_API_KEY_FORMAT" | "INVALID_API_KEY" | "AUTHENTICATION_FAILED" | "ENDPOINT_NOT_PUBLIC" | "INSUFFICIENT_SCOPE" | "INSUFFICIENT_PERMISSION" | "IP_NOT_ALLOWED" | "RATE_LIMIT_EXCEEDED" | (string & {});
230
+ declare class NateqError extends Error {
231
+ /** HTTP status, when the failure came from a response. */
232
+ readonly status?: number | undefined;
233
+ /** API error code, when the response carried one. */
234
+ readonly code?: NateqErrorCode | undefined;
235
+ /** Extra context from the API (validation details, scope lists, ...). */
236
+ readonly details?: unknown;
237
+ /** Value of the `X-Request-Id` response header, useful for support tickets. */
238
+ readonly requestId?: string | undefined;
239
+ constructor(message: string, opts?: {
240
+ status?: number | undefined;
241
+ code?: NateqErrorCode | undefined;
242
+ details?: unknown;
243
+ requestId?: string | undefined;
244
+ cause?: unknown;
245
+ });
246
+ }
247
+ /** The key is missing, malformed, revoked, or unknown (401). */
248
+ declare class NateqAuthenticationError extends NateqError {}
249
+ /**
250
+ * The key is valid but not allowed to do this (403).
251
+ *
252
+ * Common causes: the key lacks the `emails:send` scope, the endpoint is not
253
+ * exposed to API keys, or the caller's IP is outside the key's allow-list.
254
+ */
255
+ declare class NateqPermissionError extends NateqError {}
256
+ /** The request body failed validation (400/422). */
257
+ declare class NateqValidationError extends NateqError {}
258
+ /** The requested resource does not exist, or is not visible to this key (404). */
259
+ declare class NateqNotFoundError extends NateqError {}
260
+ /** A rate limit was hit (429). Nothing was sent — this is safe to retry. */
261
+ declare class NateqRateLimitError extends NateqError {
262
+ /** Seconds to wait before retrying, when the API supplies `Retry-After`. */
263
+ readonly retryAfter?: number | undefined;
264
+ constructor(message: string, opts?: ConstructorParameters<typeof NateqError>[1] & {
265
+ retryAfter?: number | undefined;
266
+ });
267
+ }
268
+ /** The API failed to handle the request (5xx). */
269
+ declare class NateqServerError extends NateqError {}
270
+ /** The request never produced a response: DNS, TLS, socket, or offline. */
271
+ declare class NateqConnectionError extends NateqError {}
272
+ /** The request exceeded the configured timeout, or was aborted by the caller. */
273
+ declare class NateqTimeoutError extends NateqError {}
274
+ /** Bad SDK usage — thrown before any network call (never carries a status). */
275
+ declare class NateqConfigurationError extends NateqError {}
276
+ //#endregion
277
+ export { Emails, type FetchLike, type ListEmailsParams, type ListEmailsResult, Nateq, NateqAuthenticationError, NateqConfigurationError, NateqConnectionError, NateqError, type NateqErrorCode, NateqNotFoundError, type NateqOptions, NateqPermissionError, NateqRateLimitError, NateqServerError, NateqTimeoutError, NateqValidationError, type OutboundEmail, type OutboundEmailStatus, type SendEmailParams, type SendEmailResult };
278
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,278 @@
1
+ //#region src/http.d.ts
2
+ /** Subset of `fetch` the SDK relies on, so callers can inject their own. */
3
+ type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
4
+ interface TransportOptions {
5
+ apiKey: string;
6
+ baseUrl: string;
7
+ timeout: number;
8
+ maxRetries: number;
9
+ fetch: FetchLike;
10
+ userAgent: string;
11
+ }
12
+ interface RequestOptions {
13
+ method: "GET" | "POST";
14
+ path: string;
15
+ query?: Record<string, unknown> | undefined;
16
+ body?: unknown;
17
+ signal?: AbortSignal | undefined;
18
+ /**
19
+ * Whether this request may be replayed after an ambiguous failure (network
20
+ * error, timeout, 5xx) — i.e. a failure where the server may already have
21
+ * acted. Only safe for requests with no side effects.
22
+ *
23
+ * A 429 is retried regardless: the API rejects rate-limited sends during
24
+ * validation, before any mail leaves, so no duplicate can result.
25
+ */
26
+ retryOnAmbiguousFailure: boolean;
27
+ }
28
+ declare class Transport {
29
+ private readonly options;
30
+ constructor(options: TransportOptions);
31
+ request<T>(req: RequestOptions): Promise<T>;
32
+ }
33
+ //#endregion
34
+ //#region src/types.d.ts
35
+ /** Delivery status of an outbound email. */
36
+ type OutboundEmailStatus = "pending" | "sending" | "sent" | "delivered" | "bounced" | "failed" | "rejected" | "opened" | "clicked";
37
+ /**
38
+ * Body of a send request.
39
+ *
40
+ * At least one of `htmlBody` / `plainTextBody` must be present. When
41
+ * `emailAddressId` and `fromEmail` are both omitted, the organization's default
42
+ * verified address is used.
43
+ */
44
+ interface SendEmailParams {
45
+ /** Verified organization address to send from. Defaults to the org default. */
46
+ emailAddressId?: string;
47
+ /** Override the From address. Must be a verified organization address. */
48
+ fromEmail?: string;
49
+ fromName?: string;
50
+ /** One or more recipients. Required. */
51
+ toEmails: string[];
52
+ ccEmails?: string[];
53
+ bccEmails?: string[];
54
+ replyTo?: string;
55
+ /** Required, 1-998 characters. */
56
+ subject: string;
57
+ htmlBody?: string;
58
+ plainTextBody?: string;
59
+ /** `Message-ID` this email replies to (threading). */
60
+ inReplyTo?: string;
61
+ /** `References` header (threading). */
62
+ references?: string;
63
+ ticketId?: string;
64
+ conversationId?: string;
65
+ contactId?: string;
66
+ /** IDs of previously uploaded files to attach. */
67
+ attachmentIds?: string[];
68
+ /** Extra custom headers. */
69
+ headers?: Record<string, string>;
70
+ }
71
+ /** Result of a successful send. */
72
+ interface SendEmailResult {
73
+ id: string;
74
+ providerMessageId?: string;
75
+ status: OutboundEmailStatus;
76
+ createdAt: string;
77
+ }
78
+ /** A stored outbound email, as returned by `get` and `list`. */
79
+ interface OutboundEmail {
80
+ id: string;
81
+ organizationId: string;
82
+ emailAddressId?: string;
83
+ messageId?: string;
84
+ providerMessageId?: string;
85
+ fromEmail: string;
86
+ fromName?: string;
87
+ toEmails: string[];
88
+ ccEmails?: string[];
89
+ bccEmails?: string[];
90
+ subject?: string;
91
+ htmlBody?: string;
92
+ plainTextBody?: string;
93
+ status: OutboundEmailStatus;
94
+ ticketId?: string;
95
+ conversationId?: string;
96
+ contactId?: string;
97
+ sentByUserId?: string;
98
+ isAutomatic?: boolean;
99
+ deliveredAt?: string;
100
+ bouncedAt?: string;
101
+ openedAt?: string;
102
+ openCount?: number;
103
+ bounceReason?: string;
104
+ createdAt: string;
105
+ updatedAt: string;
106
+ [key: string]: unknown;
107
+ }
108
+ /** Filters for `emails.list`. */
109
+ interface ListEmailsParams {
110
+ emailAddressId?: string;
111
+ status?: OutboundEmailStatus;
112
+ fromEmail?: string;
113
+ /** Match against any recipient. */
114
+ toEmail?: string;
115
+ ticketId?: string;
116
+ conversationId?: string;
117
+ contactId?: string;
118
+ sentByUserId?: string;
119
+ isAutomatic?: boolean;
120
+ /** Defaults to 50 server-side; capped at 100. */
121
+ limit?: number;
122
+ offset?: number;
123
+ }
124
+ /** A page of outbound emails. */
125
+ interface ListEmailsResult {
126
+ emails: OutboundEmail[];
127
+ total: number;
128
+ limit: number;
129
+ offset: number;
130
+ }
131
+ //#endregion
132
+ //#region src/emails.d.ts
133
+ /** Outbound email operations. Reachable at `client.emails`. */
134
+ declare class Emails {
135
+ private readonly transport;
136
+ constructor(transport: Transport);
137
+ /**
138
+ * Sends an email, returning once the API has accepted it. Delivery itself is
139
+ * asynchronous — poll {@link get} or subscribe to a webhook for the outcome.
140
+ *
141
+ * Requires an API key with the `emails:send` scope.
142
+ *
143
+ * A failed send is **not** replayed automatically unless the API rejected it
144
+ * outright (429), because a request that fails after reaching the server may
145
+ * already have sent the mail. On a timeout or 5xx, look the email up with
146
+ * {@link list} before retrying, or you risk sending twice.
147
+ *
148
+ * @throws {NateqValidationError} if the params are unusable, before any call.
149
+ * @throws {NateqPermissionError} if the key lacks the `emails:send` scope.
150
+ */
151
+ send(params: SendEmailParams, opts?: {
152
+ signal?: AbortSignal;
153
+ }): Promise<SendEmailResult>;
154
+ /**
155
+ * Fetches a single sent email by id, including delivery status.
156
+ *
157
+ * Requires an API key with the `emails:read` scope.
158
+ */
159
+ get(id: string, opts?: {
160
+ signal?: AbortSignal;
161
+ }): Promise<OutboundEmail>;
162
+ /**
163
+ * Lists sent emails, newest first. `limit` defaults to 50 and is capped at 100.
164
+ *
165
+ * Requires an API key with the `emails:read` scope.
166
+ */
167
+ list(params?: ListEmailsParams, opts?: {
168
+ signal?: AbortSignal;
169
+ }): Promise<ListEmailsResult>;
170
+ }
171
+ //#endregion
172
+ //#region src/client.d.ts
173
+ interface NateqOptions {
174
+ /**
175
+ * Your Nateq API key. Defaults to `process.env.NATEQ_API_KEY`.
176
+ *
177
+ * Load this from the environment or a secret manager — never commit it, and
178
+ * never ship it to a browser: the key carries your organization's full scope
179
+ * grant and this SDK is server-side only.
180
+ */
181
+ apiKey?: string;
182
+ /** API base URL. Defaults to `process.env.NATEQ_BASE_URL` or production. */
183
+ baseUrl?: string;
184
+ /** Per-request timeout in milliseconds. Defaults to 30000. */
185
+ timeout?: number;
186
+ /** Retries after a retriable failure. Defaults to 2. Set 0 to disable. */
187
+ maxRetries?: number;
188
+ /** Custom fetch, for tests or proxies. Defaults to the global `fetch`. */
189
+ fetch?: FetchLike;
190
+ }
191
+ /**
192
+ * The Nateq API client.
193
+ *
194
+ * ```ts
195
+ * import { Nateq } from "@nateq/sdk";
196
+ *
197
+ * const nateq = new Nateq(); // reads NATEQ_API_KEY
198
+ *
199
+ * await nateq.emails.send({
200
+ * toEmails: ["customer@example.com"],
201
+ * subject: "Welcome aboard",
202
+ * htmlBody: "<p>Thanks for signing up.</p>",
203
+ * });
204
+ * ```
205
+ *
206
+ * The instance holds your API key, so it is never safe to serialise: `console.log`,
207
+ * `JSON.stringify`, and util.inspect all render it with the key redacted.
208
+ */
209
+ declare class Nateq {
210
+ #private;
211
+ /** Outbound email: send, get, list. */
212
+ readonly emails: Emails;
213
+ readonly baseUrl: string;
214
+ constructor(options?: NateqOptions);
215
+ /** Last four characters of the key, for logging which key is in use. */
216
+ get apiKeyLast4(): string;
217
+ toJSON(): Record<string, unknown>;
218
+ }
219
+ //#endregion
220
+ //#region src/errors.d.ts
221
+ /**
222
+ * Error types raised by the Nateq SDK.
223
+ *
224
+ * Every error extends {@link NateqError}, so a single `catch (err) { if (err
225
+ * instanceof NateqError) ... }` covers the whole surface. No error ever carries
226
+ * the API key: see `redact` in `http.ts`.
227
+ */
228
+ /** Machine-readable code returned by the API, when it sends one. */
229
+ type NateqErrorCode = "MISSING_API_KEY" | "INVALID_AUTH_FORMAT" | "INVALID_API_KEY_FORMAT" | "INVALID_API_KEY" | "AUTHENTICATION_FAILED" | "ENDPOINT_NOT_PUBLIC" | "INSUFFICIENT_SCOPE" | "INSUFFICIENT_PERMISSION" | "IP_NOT_ALLOWED" | "RATE_LIMIT_EXCEEDED" | (string & {});
230
+ declare class NateqError extends Error {
231
+ /** HTTP status, when the failure came from a response. */
232
+ readonly status?: number | undefined;
233
+ /** API error code, when the response carried one. */
234
+ readonly code?: NateqErrorCode | undefined;
235
+ /** Extra context from the API (validation details, scope lists, ...). */
236
+ readonly details?: unknown;
237
+ /** Value of the `X-Request-Id` response header, useful for support tickets. */
238
+ readonly requestId?: string | undefined;
239
+ constructor(message: string, opts?: {
240
+ status?: number | undefined;
241
+ code?: NateqErrorCode | undefined;
242
+ details?: unknown;
243
+ requestId?: string | undefined;
244
+ cause?: unknown;
245
+ });
246
+ }
247
+ /** The key is missing, malformed, revoked, or unknown (401). */
248
+ declare class NateqAuthenticationError extends NateqError {}
249
+ /**
250
+ * The key is valid but not allowed to do this (403).
251
+ *
252
+ * Common causes: the key lacks the `emails:send` scope, the endpoint is not
253
+ * exposed to API keys, or the caller's IP is outside the key's allow-list.
254
+ */
255
+ declare class NateqPermissionError extends NateqError {}
256
+ /** The request body failed validation (400/422). */
257
+ declare class NateqValidationError extends NateqError {}
258
+ /** The requested resource does not exist, or is not visible to this key (404). */
259
+ declare class NateqNotFoundError extends NateqError {}
260
+ /** A rate limit was hit (429). Nothing was sent — this is safe to retry. */
261
+ declare class NateqRateLimitError extends NateqError {
262
+ /** Seconds to wait before retrying, when the API supplies `Retry-After`. */
263
+ readonly retryAfter?: number | undefined;
264
+ constructor(message: string, opts?: ConstructorParameters<typeof NateqError>[1] & {
265
+ retryAfter?: number | undefined;
266
+ });
267
+ }
268
+ /** The API failed to handle the request (5xx). */
269
+ declare class NateqServerError extends NateqError {}
270
+ /** The request never produced a response: DNS, TLS, socket, or offline. */
271
+ declare class NateqConnectionError extends NateqError {}
272
+ /** The request exceeded the configured timeout, or was aborted by the caller. */
273
+ declare class NateqTimeoutError extends NateqError {}
274
+ /** Bad SDK usage — thrown before any network call (never carries a status). */
275
+ declare class NateqConfigurationError extends NateqError {}
276
+ //#endregion
277
+ export { Emails, type FetchLike, type ListEmailsParams, type ListEmailsResult, Nateq, NateqAuthenticationError, NateqConfigurationError, NateqConnectionError, NateqError, type NateqErrorCode, NateqNotFoundError, type NateqOptions, NateqPermissionError, NateqRateLimitError, NateqServerError, NateqTimeoutError, NateqValidationError, type OutboundEmail, type OutboundEmailStatus, type SendEmailParams, type SendEmailResult };
278
+ //# sourceMappingURL=index.d.ts.map