tosend 1.1.0 → 1.2.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.
package/README.md CHANGED
@@ -76,6 +76,27 @@ const response = await tosend.send({
76
76
  });
77
77
  ```
78
78
 
79
+ ### Inline Images
80
+
81
+ Give an attachment a `content_id` and reference it from the HTML with `cid:`:
82
+
83
+ ```typescript
84
+ await tosend.send({
85
+ from: { email: 'hello@yourdomain.com' },
86
+ to: [{ email: 'user@example.com' }],
87
+ subject: 'Welcome',
88
+ html: '<img src="cid:logo@yourdomain.com" alt="Logo">',
89
+ attachments: [
90
+ {
91
+ type: 'image/png',
92
+ name: 'logo.png',
93
+ content: fs.readFileSync('logo.png').toString('base64'),
94
+ content_id: 'logo@yourdomain.com',
95
+ },
96
+ ],
97
+ });
98
+ ```
99
+
79
100
  ### Batch Sending
80
101
 
81
102
  Send multiple emails in a single request:
@@ -148,7 +169,7 @@ await client.send({
148
169
  });
149
170
  ```
150
171
 
151
- Also available: `getInfo` (checks the key and lists its scopes), `listDomains`, `deleteDomain`, `listEmails`, `getEmail`, `listSuppressions`, `deleteSuppression`, `listWebhooks`, `createWebhook`, `updateWebhook`, `deleteWebhook`, `listSendingKeys`, `deleteSendingKey`, `listTeams`, `createTeam`, `updateTeam` and `batch`. List methods take `page` and `per_page` and return `{ data, total, page, per_page }`.
172
+ Also available: `getInfo` (checks the key and lists its scopes), `listDomains`, `deleteDomain`, `listEmails`, `getEmail`, `listSuppressions`, `deleteSuppression`, `listWebhooks`, `createWebhook`, `updateWebhook`, `deleteWebhook`, `listSendingKeys`, `deleteSendingKey`, `listTeams`, `createTeam` (`createTeam('Acme', { ses_region: 'eu-central-1' })` for an EU team; the region can't be changed later), `updateTeam` and `batch`. List methods take `page` and `per_page` and return `{ data, total, page, per_page }`.
152
173
 
153
174
  ## Configuration
154
175
 
package/dist/index.d.mts CHANGED
@@ -15,6 +15,8 @@ interface Attachment {
15
15
  name: string;
16
16
  /** Base64 encoded content */
17
17
  content: string;
18
+ /** Embeds the file inline: reference it in the HTML as `cid:<content_id>` */
19
+ content_id?: string;
18
20
  }
19
21
  interface SendEmailParams {
20
22
  /** Sender address */
@@ -70,10 +72,10 @@ interface AccountInfo {
70
72
  monthly_email_limit: number;
71
73
  limit_per_second: number;
72
74
  ses_region: string;
73
- /** Fraction between 0 and 1, not a percentage */
74
- bounce_rate: number;
75
- /** Fraction between 0 and 1, not a percentage */
76
- complaint_rate: number;
75
+ /** @deprecated Not returned to a sending key. Use `new ToSendAdmin(key).getInfo()`. */
76
+ bounce_rate?: number;
77
+ /** @deprecated Not returned to a sending key. Use `new ToSendAdmin(key).getInfo()`. */
78
+ complaint_rate?: number;
77
79
  ses_status: string;
78
80
  };
79
81
  domains: Domain[];
@@ -322,7 +324,9 @@ interface AdminInfo {
322
324
  credits_shared: boolean;
323
325
  limit_per_second: number;
324
326
  ses_region: string;
327
+ /** Last 30 days, fraction between 0 and 1 (0.004 = 0.4%) */
325
328
  bounce_rate: number;
329
+ /** Last 30 days, fraction between 0 and 1 */
326
330
  complaint_rate: number;
327
331
  };
328
332
  key: {
@@ -345,6 +349,8 @@ interface SuppressionReactivation {
345
349
  remaining_today: number;
346
350
  daily_limit: number;
347
351
  }
352
+ /** Where a team sends from. Fixed for the life of the team. */
353
+ type SesRegion = 'us-east-1' | 'eu-central-1';
348
354
  interface TeamList {
349
355
  account: {
350
356
  id: number;
@@ -356,6 +362,7 @@ interface TeamList {
356
362
  id: number;
357
363
  name: string | null;
358
364
  status: string;
365
+ ses_region: SesRegion;
359
366
  }>;
360
367
  /** Credits pool at the parent; child teams have no separate balance */
361
368
  credits_shared: boolean;
@@ -482,11 +489,17 @@ declare class ToSendAdmin {
482
489
  createSendingKey(name: string, domain?: string): Promise<CreatedSendingKey>;
483
490
  deleteSendingKey(id: number): Promise<void>;
484
491
  listTeams(): Promise<TeamList>;
485
- /** Only the parent account can create teams. */
486
- createTeam(name: string): Promise<{
492
+ /**
493
+ * Only the parent account can create teams. `ses_region` defaults to
494
+ * 'us-east-1' and cannot be changed once the team exists.
495
+ */
496
+ createTeam(name: string, options?: {
497
+ ses_region?: SesRegion;
498
+ }): Promise<{
487
499
  id: number;
488
500
  name: string;
489
501
  status: string;
502
+ ses_region: SesRegion;
490
503
  }>;
491
504
  updateTeam(id: number, params: {
492
505
  name?: string;
@@ -499,4 +512,4 @@ declare class ToSendAdmin {
499
512
  private request;
500
513
  }
501
514
 
502
- export { type AccountInfo, type Address, type AdminConfig, type AdminDomain, type AdminDomainDetail, type AdminInfo, type Attachment, type BatchEmailResponse, type BatchEmailResult, type CreatedDomain, type CreatedSendingKey, type DnsRecord, type Domain, type EmailLog, type ErrorType, type ListLogsParams, type Paginated, type SendEmailParams, type SendEmailResponse, type SendingKey, type Suppression, type SuppressionReactivation, type TeamList, ToSend, ToSendAdmin, type ToSendConfig, ToSendError, type ToSendErrorResponse, type ValidationErrors, type Webhook, type WebhookEvent, ToSend as default };
515
+ export { type AccountInfo, type Address, type AdminConfig, type AdminDomain, type AdminDomainDetail, type AdminInfo, type Attachment, type BatchEmailResponse, type BatchEmailResult, type CreatedDomain, type CreatedSendingKey, type DnsRecord, type Domain, type EmailLog, type ErrorType, type ListLogsParams, type Paginated, type SendEmailParams, type SendEmailResponse, type SendingKey, type SesRegion, type Suppression, type SuppressionReactivation, type TeamList, ToSend, ToSendAdmin, type ToSendConfig, ToSendError, type ToSendErrorResponse, type ValidationErrors, type Webhook, type WebhookEvent, ToSend as default };
package/dist/index.d.ts CHANGED
@@ -15,6 +15,8 @@ interface Attachment {
15
15
  name: string;
16
16
  /** Base64 encoded content */
17
17
  content: string;
18
+ /** Embeds the file inline: reference it in the HTML as `cid:<content_id>` */
19
+ content_id?: string;
18
20
  }
19
21
  interface SendEmailParams {
20
22
  /** Sender address */
@@ -70,10 +72,10 @@ interface AccountInfo {
70
72
  monthly_email_limit: number;
71
73
  limit_per_second: number;
72
74
  ses_region: string;
73
- /** Fraction between 0 and 1, not a percentage */
74
- bounce_rate: number;
75
- /** Fraction between 0 and 1, not a percentage */
76
- complaint_rate: number;
75
+ /** @deprecated Not returned to a sending key. Use `new ToSendAdmin(key).getInfo()`. */
76
+ bounce_rate?: number;
77
+ /** @deprecated Not returned to a sending key. Use `new ToSendAdmin(key).getInfo()`. */
78
+ complaint_rate?: number;
77
79
  ses_status: string;
78
80
  };
79
81
  domains: Domain[];
@@ -322,7 +324,9 @@ interface AdminInfo {
322
324
  credits_shared: boolean;
323
325
  limit_per_second: number;
324
326
  ses_region: string;
327
+ /** Last 30 days, fraction between 0 and 1 (0.004 = 0.4%) */
325
328
  bounce_rate: number;
329
+ /** Last 30 days, fraction between 0 and 1 */
326
330
  complaint_rate: number;
327
331
  };
328
332
  key: {
@@ -345,6 +349,8 @@ interface SuppressionReactivation {
345
349
  remaining_today: number;
346
350
  daily_limit: number;
347
351
  }
352
+ /** Where a team sends from. Fixed for the life of the team. */
353
+ type SesRegion = 'us-east-1' | 'eu-central-1';
348
354
  interface TeamList {
349
355
  account: {
350
356
  id: number;
@@ -356,6 +362,7 @@ interface TeamList {
356
362
  id: number;
357
363
  name: string | null;
358
364
  status: string;
365
+ ses_region: SesRegion;
359
366
  }>;
360
367
  /** Credits pool at the parent; child teams have no separate balance */
361
368
  credits_shared: boolean;
@@ -482,11 +489,17 @@ declare class ToSendAdmin {
482
489
  createSendingKey(name: string, domain?: string): Promise<CreatedSendingKey>;
483
490
  deleteSendingKey(id: number): Promise<void>;
484
491
  listTeams(): Promise<TeamList>;
485
- /** Only the parent account can create teams. */
486
- createTeam(name: string): Promise<{
492
+ /**
493
+ * Only the parent account can create teams. `ses_region` defaults to
494
+ * 'us-east-1' and cannot be changed once the team exists.
495
+ */
496
+ createTeam(name: string, options?: {
497
+ ses_region?: SesRegion;
498
+ }): Promise<{
487
499
  id: number;
488
500
  name: string;
489
501
  status: string;
502
+ ses_region: SesRegion;
490
503
  }>;
491
504
  updateTeam(id: number, params: {
492
505
  name?: string;
@@ -499,4 +512,4 @@ declare class ToSendAdmin {
499
512
  private request;
500
513
  }
501
514
 
502
- export { type AccountInfo, type Address, type AdminConfig, type AdminDomain, type AdminDomainDetail, type AdminInfo, type Attachment, type BatchEmailResponse, type BatchEmailResult, type CreatedDomain, type CreatedSendingKey, type DnsRecord, type Domain, type EmailLog, type ErrorType, type ListLogsParams, type Paginated, type SendEmailParams, type SendEmailResponse, type SendingKey, type Suppression, type SuppressionReactivation, type TeamList, ToSend, ToSendAdmin, type ToSendConfig, ToSendError, type ToSendErrorResponse, type ValidationErrors, type Webhook, type WebhookEvent, ToSend as default };
515
+ export { type AccountInfo, type Address, type AdminConfig, type AdminDomain, type AdminDomainDetail, type AdminInfo, type Attachment, type BatchEmailResponse, type BatchEmailResult, type CreatedDomain, type CreatedSendingKey, type DnsRecord, type Domain, type EmailLog, type ErrorType, type ListLogsParams, type Paginated, type SendEmailParams, type SendEmailResponse, type SendingKey, type SesRegion, type Suppression, type SuppressionReactivation, type TeamList, ToSend, ToSendAdmin, type ToSendConfig, ToSendError, type ToSendErrorResponse, type ValidationErrors, type Webhook, type WebhookEvent, ToSend as default };
package/dist/index.js CHANGED
@@ -378,9 +378,14 @@ var ToSendAdmin = class _ToSendAdmin {
378
378
  const res = await this.request("GET", "/v2/teams");
379
379
  return res.data;
380
380
  }
381
- /** Only the parent account can create teams. */
382
- async createTeam(name) {
383
- const res = await this.request("POST", "/v2/teams", { name });
381
+ /**
382
+ * Only the parent account can create teams. `ses_region` defaults to
383
+ * 'us-east-1' and cannot be changed once the team exists.
384
+ */
385
+ async createTeam(name, options = {}) {
386
+ const body = { name };
387
+ if (options.ses_region) body.ses_region = options.ses_region;
388
+ const res = await this.request("POST", "/v2/teams", body);
384
389
  return res.data;
385
390
  }
386
391
  async updateTeam(id, params) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * ToSend Node.js SDK\n *\n * Official SDK for the ToSend email API.\n * @see https://tosend.com/docs\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface Address {\n email: string;\n name?: string;\n}\n\nexport interface Attachment {\n /** MIME type (e.g., 'application/pdf') */\n type: string;\n /** Filename */\n name: string;\n /** Base64 encoded content */\n content: string;\n}\n\nexport interface SendEmailParams {\n /** Sender address */\n from: Address;\n /** Recipients (max 50) */\n to: Address[];\n /** Email subject */\n subject: string;\n /** HTML content */\n html?: string;\n /** Plain text content */\n text?: string;\n /** CC recipients */\n cc?: Address[];\n /** BCC recipients */\n bcc?: Address[];\n /** Reply-to address */\n reply_to?: Address;\n /** Custom headers */\n headers?: Record<string, string>;\n /** File attachments */\n attachments?: Attachment[];\n}\n\nexport interface SendEmailResponse {\n message_id: string;\n}\n\nexport interface BatchEmailResult {\n status: 'success' | 'error' | 'spam';\n message_id?: string;\n message?: string;\n errors?: ValidationErrors;\n}\n\nexport interface BatchEmailResponse {\n results: BatchEmailResult[];\n}\n\nexport interface Domain {\n id: number;\n domain_name: string;\n verification_status: string;\n /** 1 for the account's default domain, else 0 */\n is_default: number;\n total_sent: number;\n total_bounced: number;\n total_complained: number;\n}\n\nexport interface AccountInfo {\n account: {\n id: number;\n status: string;\n plan_type: string;\n /** null means the account does not consume credits */\n credit_balance: number | null;\n monthly_email_limit: number;\n limit_per_second: number;\n ses_region: string;\n /** Fraction between 0 and 1, not a percentage */\n bounce_rate: number;\n /** Fraction between 0 and 1, not a percentage */\n complaint_rate: number;\n ses_status: string;\n };\n domains: Domain[];\n}\n\nexport interface ToSendErrorResponse {\n status_code: number;\n error_type: ErrorType;\n message: string;\n /**\n * Field-level validation detail.\n *\n * The sending API returns a nested map (`{subject: {required: \"...\"}}`);\n * the Worker's ApiError returns a flat one (`{name: \"required\"}`). Both\n * shapes reach this type, so consumers must not assume either — see\n * ToSendError.errors.\n */\n errors?: ValidationErrors;\n}\n\n/**\n * Either shape the API may return for `errors`.\n *\n * ToSendError declared the nested form only, so `err.errors.name` was typed\n * as an object while the Worker actually sends the string `'required'`. That\n * compiled because the response was cast, and would have surfaced as an\n * undefined property at runtime.\n */\nexport type ValidationErrors = Record<string, string | Record<string, string>>;\n\nexport type ErrorType =\n | 'bad_request'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'validation_error'\n | 'rate_limit_exceeded'\n | 'internal_error';\n\nexport interface ToSendConfig {\n /** Your ToSend API key */\n apiKey: string;\n /** Custom base URL (optional) */\n baseUrl?: string;\n /** Request timeout in ms (default: 30000) */\n timeout?: number;\n}\n\n// ============================================================================\n// Error Class\n// ============================================================================\n\nexport class ToSendError extends Error {\n readonly code: number;\n readonly errorType: ErrorType;\n /** See ValidationErrors — the value may be a string or a nested map. */\n readonly errors: ValidationErrors;\n\n private static readonly ERROR_TYPE_MAP: Record<number, ErrorType> = {\n 400: 'bad_request',\n 401: 'unauthorized',\n 403: 'forbidden',\n 404: 'not_found',\n 422: 'validation_error',\n 429: 'rate_limit_exceeded',\n };\n\n constructor(\n message: string,\n code: number = 0,\n errors: ValidationErrors = {},\n errorType?: ErrorType\n ) {\n super(message);\n this.name = 'ToSendError';\n this.code = code;\n this.errors = errors;\n this.errorType = errorType ?? ToSendError.ERROR_TYPE_MAP[code] ?? 'internal_error';\n }\n\n get isValidationError(): boolean {\n return this.errorType === 'validation_error' || this.code === 422;\n }\n\n get isAuthenticationError(): boolean {\n return (\n this.errorType === 'unauthorized' ||\n this.errorType === 'forbidden' ||\n this.code === 401 ||\n this.code === 403\n );\n }\n\n get isRateLimitError(): boolean {\n return this.errorType === 'rate_limit_exceeded' || this.code === 429;\n }\n}\n\n// ============================================================================\n// Main Client\n// ============================================================================\n\nexport class ToSend {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: string | ToSendConfig) {\n if (typeof config === 'string') {\n this.apiKey = config;\n this.baseUrl = 'https://api.tosend.com';\n this.timeout = 30000;\n } else {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl?.replace(/\\/$/, '') ?? 'https://api.tosend.com';\n this.timeout = config.timeout ?? 30000;\n }\n\n if (!this.apiKey?.trim()) {\n throw new ToSendError('API key is required', 401);\n }\n }\n\n /**\n * Send a single email.\n *\n * @example\n * ```ts\n * const response = await tosend.send({\n * from: { email: 'hello@example.com', name: 'My App' },\n * to: [{ email: 'user@example.com' }],\n * subject: 'Hello!',\n * html: '<p>Welcome!</p>'\n * });\n * console.log(response.message_id);\n * ```\n */\n async send(params: SendEmailParams): Promise<SendEmailResponse> {\n this.validateEmailParams(params);\n return this.request<SendEmailResponse>('POST', '/v2/emails', params);\n }\n\n /**\n * Send multiple emails in a single request.\n *\n * @example\n * ```ts\n * const response = await tosend.batch([\n * {\n * from: { email: 'hello@example.com' },\n * to: [{ email: 'user1@example.com' }],\n * subject: 'Hello User 1',\n * html: '<p>Hi!</p>'\n * },\n * {\n * from: { email: 'hello@example.com' },\n * to: [{ email: 'user2@example.com' }],\n * subject: 'Hello User 2',\n * html: '<p>Hi!</p>'\n * }\n * ]);\n *\n * response.results.forEach(result => {\n * if (result.status === 'success') {\n * console.log('Sent:', result.message_id);\n * }\n * });\n * ```\n */\n async batch(emails: SendEmailParams[]): Promise<BatchEmailResponse> {\n if (!emails?.length) {\n throw new ToSendError('Emails array is required and cannot be empty', 422, {\n emails: { required: 'At least one email is required' },\n });\n }\n\n emails.forEach((email, index) => {\n try {\n this.validateEmailParams(email);\n } catch (error) {\n if (error instanceof ToSendError) {\n throw new ToSendError(\n `Email at index ${index}: ${error.message}`,\n error.code,\n error.errors\n );\n }\n throw error;\n }\n });\n\n return this.request<BatchEmailResponse>('POST', '/v2/emails/batch', { emails });\n }\n\n /**\n * Get account information and domains.\n *\n * @example\n * ```ts\n * const info = await tosend.getAccountInfo();\n * console.log(info.account.title);\n * console.log(info.account.emails_usage_this_month);\n * info.domains.forEach(d => console.log(d.domain_name, d.verification_status));\n * ```\n */\n async getAccountInfo(): Promise<AccountInfo> {\n return this.request<AccountInfo>('GET', '/v2/info');\n }\n\n // ==========================================================================\n // Private Methods\n // ==========================================================================\n\n private validateEmailParams(params: SendEmailParams): void {\n const errors: Record<string, Record<string, string>> = {};\n\n if (!params.from?.email) {\n errors.from = { required: 'From email is required' };\n } else if (!this.isValidEmail(params.from.email)) {\n errors.from = { invalid: 'From email is invalid' };\n }\n\n if (!params.to?.length) {\n errors.to = { required: 'To is required' };\n } else {\n const hasValidRecipient = params.to.some(\n (r) => r.email && this.isValidEmail(r.email)\n );\n if (!hasValidRecipient) {\n errors.to = { invalid: 'At least one valid recipient email is required' };\n }\n }\n\n if (!params.subject) {\n errors.subject = { required: 'Subject is required' };\n }\n\n if (!params.html && !params.text) {\n errors.content = { required: 'Either html or text content is required' };\n }\n\n if (Object.keys(errors).length > 0) {\n const firstError = Object.values(errors)[0];\n const message = Object.values(firstError)[0];\n throw new ToSendError(message, 422, errors);\n }\n }\n\n private isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n }\n\n private async request<T>(\n method: 'GET' | 'POST',\n endpoint: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const data = await response.json() as T & {\n message?: string;\n errors?: ValidationErrors;\n error_type?: ErrorType;\n };\n\n if (!response.ok) {\n throw new ToSendError(\n data.message ?? 'Unknown error',\n response.status,\n data.errors ?? {},\n data.error_type\n );\n }\n\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ToSendError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new ToSendError('Request timeout', 408);\n }\n throw new ToSendError(error.message);\n }\n\n throw new ToSendError('Unknown error');\n }\n }\n}\n\n// ============================================================================\n// Admin API Types\n// ============================================================================\n\nexport interface AdminDomain {\n /** Domain identifier used across the Admin API */\n id: string;\n domain_name: string;\n verification_status: 'draft' | 'pending' | 'verified' | 'failed';\n is_default: boolean;\n ses_region: string | null;\n total_sent: number;\n total_bounced: number;\n total_complained: number;\n updated_at: string;\n}\n\nexport interface DnsRecord {\n type: 'TXT' | 'MX';\n name: string;\n content: string;\n TTL: string;\n priority: string;\n /**\n * NOT returned by the Admin API.\n *\n * The domain-detail endpoint does not resolve DNS on your behalf (that\n * would make a GET fire four lookups and a write), so there is no\n * per-record status. Poll `verification_status` on the domain instead —\n * it is the result of the automatic 10-minute check.\n *\n * @deprecated Always undefined. Kept so existing code still compiles.\n */\n status?: string;\n}\n\nexport interface CreatedDomain {\n /** Same identifier the list and detail endpoints use */\n id: string;\n domain_name: string;\n verification_status: string;\n created_at: string;\n}\n\nexport interface AdminDomainDetail {\n id: string;\n domain_name: string;\n verification_status: string;\n dmarc_policy: string | null;\n created_at: string;\n dns_records: {\n dkim: DnsRecord;\n spf: DnsRecord[];\n };\n /** Whether the automatic 10-minute DNS check still covers this domain */\n auto_verification: { active: boolean; reason: string };\n}\n\nexport interface EmailLog {\n /** Same value POST /v2/emails returned as message_id */\n id: string;\n status: string;\n subject: string | null;\n from: string | null;\n from_name: string | null;\n to: Array<{ email: string; name?: string }>;\n reply_to: string | null;\n recipients_count: number;\n error: string | null;\n has_attachments: boolean;\n sent_at: string | null;\n created_at: string;\n}\n\nexport interface Paginated<T> {\n data: T[];\n total: number;\n page: number;\n per_page: number;\n}\n\nexport interface ListLogsParams {\n page?: number;\n /** 1-100, default 25 */\n per_page?: number;\n status?: string;\n /** Domain name or domain id */\n domain?: string;\n from?: string;\n to?: string;\n search?: string;\n}\n\nexport interface Suppression {\n email: string;\n bounce_sub_type: string | null;\n bounce_reason: string | null;\n hard_bounced_at: string | null;\n last_complaint_at: string | null;\n complaint_count: number;\n created_at: string;\n updated_at: string;\n}\n\n/** The only events toSend emits. Delivery, open and click do not exist. */\nexport type WebhookEvent = 'bounced' | 'complaint';\n\nexport interface Webhook {\n id: number;\n title: string | null;\n url: string;\n events: WebhookEvent[];\n status: string;\n include_message: boolean;\n created_at: string;\n updated_at: string;\n}\n\nexport interface SendingKey {\n id: number;\n title: string | null;\n /** Masked. The full key is returned only by createSendingKey(). */\n key: string;\n status: string;\n domain: string | null;\n created_at: string;\n}\n\nexport interface CreatedSendingKey {\n id: number;\n name: string;\n /** Shown once. Store it now — it cannot be retrieved later. */\n api_key: string;\n created_at: string;\n}\n\n/** GET /v2/info with an admin key */\nexport interface AdminInfo {\n account: {\n id: number;\n status: string;\n plan_type: string;\n /** The pooled balance for the whole account, never a child team's own */\n credit_balance: number | null;\n credits_shared: boolean;\n limit_per_second: number;\n ses_region: string;\n bounce_rate: number;\n complaint_rate: number;\n };\n key: {\n id: number;\n title: string;\n type: 'admin';\n /** The team this request acted on */\n acting_on: number;\n /** The team that owns the key */\n owned_by: number;\n scopes: string[];\n };\n /** Present only when the key has `domains:read` */\n domains?: AdminDomain[];\n}\n\nexport interface SuppressionReactivation {\n deleted: boolean;\n message: string;\n used_today: number;\n remaining_today: number;\n daily_limit: number;\n}\n\nexport interface TeamList {\n account: { id: number; name: string | null; status: string; credit_balance: number | null };\n teams: Array<{ id: number; name: string | null; status: string }>;\n /** Credits pool at the parent; child teams have no separate balance */\n credits_shared: boolean;\n}\n\nexport interface AdminConfig {\n /** Your admin API key (tsend_admin_...) */\n apiKey: string;\n /** Act on a child team by default. Overridable per call. */\n accountId?: number;\n baseUrl?: string;\n timeout?: number;\n}\n\n// ============================================================================\n// Admin Client\n// ============================================================================\n\n/**\n * Account-level API client.\n *\n * Deliberately a separate class from `ToSend`: admin keys and sending keys are\n * different credentials and are not interchangeable. A sending key cannot\n * manage the account, and an admin key can only send when it holds the\n * `emails:send` scope. Keeping them on separate clients means the mistake is\n * caught at the type level rather than by a 401 in production.\n *\n * @example\n * ```ts\n * const admin = new ToSendAdmin(process.env.TOSEND_ADMIN_KEY!);\n *\n * const domain = await admin.createDomain('client.example.com');\n * const detail = await admin.getDomain(domain.id);\n * console.log(detail.dns_records.dkim);\n * // No verify call needed — toSend checks DNS every 10 minutes.\n * ```\n */\nexport class ToSendAdmin {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n private readonly accountId?: number;\n\n constructor(config: string | AdminConfig) {\n if (typeof config === 'string') {\n this.apiKey = config;\n this.baseUrl = 'https://api.tosend.com';\n this.timeout = 30000;\n } else {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl?.replace(/\\/$/, '') ?? 'https://api.tosend.com';\n this.timeout = config.timeout ?? 30000;\n this.accountId = config.accountId;\n }\n\n if (!this.apiKey?.trim()) {\n throw new ToSendError('Admin API key is required', 401);\n }\n\n // Caught here rather than as a 401 from the server, because pasting a\n // sending key is the likeliest mistake and the message should say so.\n if (!this.apiKey.startsWith('tsend_admin_')) {\n throw new ToSendError(\n 'This is not an admin API key. Admin keys start with \"tsend_admin_\" and are created in the dashboard under API Keys.',\n 401\n );\n }\n }\n\n /**\n * The team this key acts on, the key itself and its scopes. Needs no scope,\n * so it is the way to check a key works.\n */\n async getInfo(): Promise<AdminInfo> {\n return this.request<AdminInfo>('GET', '/v2/info');\n }\n\n /** Return a client scoped to one of your child teams. */\n forAccount(accountId: number): ToSendAdmin {\n return new ToSendAdmin({\n apiKey: this.apiKey,\n baseUrl: this.baseUrl,\n timeout: this.timeout,\n accountId,\n });\n }\n\n // ── Sending (requires the emails:send scope) ──\n\n /**\n * Send an email as the targeted team (the key's own, or one set with\n * forAccount()). The key must hold the `emails:send` scope.\n */\n async send(params: SendEmailParams): Promise<SendEmailResponse> {\n return this.request<SendEmailResponse>('POST', '/v2/emails', params);\n }\n\n /** Send up to 100 emails in one request; same scope and targeting as send(). */\n async batch(emails: SendEmailParams[]): Promise<BatchEmailResponse> {\n return this.request<BatchEmailResponse>('POST', '/v2/emails/batch', { emails });\n }\n\n // ── Domains ──\n\n async listDomains(params: { page?: number; per_page?: number } = {}): Promise<Paginated<AdminDomain>> {\n return this.request<Paginated<AdminDomain>>('GET', '/v2/domains', undefined, params);\n }\n\n /**\n * Add a sending domain.\n *\n * The domain starts in `draft`. There is NO verify call to make: toSend\n * checks public DNS every 10 minutes and provisions the domain as soon as\n * the records resolve.\n *\n * A duplicate throws a 409. Treat that as \"already exists, go read it\" —\n * domain names are globally unique, so a retry after a timeout hits this\n * for a domain your first call created successfully.\n */\n async createDomain(name: string): Promise<CreatedDomain> {\n const res = await this.request<{ data: CreatedDomain }>('POST', '/v2/domains', { name });\n return res.data;\n }\n\n /** Domain detail plus the DNS records your customer must publish. */\n async getDomain(id: string): Promise<AdminDomainDetail> {\n const res = await this.request<{ data: AdminDomainDetail }>('GET', `/v2/domains/${encodeURIComponent(id)}`);\n return res.data;\n }\n\n /**\n * Remove a domain.\n *\n * Does NOT remove anything from your customer's DNS zone. Records left\n * published can allow the domain to be re-verified elsewhere, so clean them\n * up in your own workflow.\n */\n async deleteDomain(id: string): Promise<void> {\n await this.request('DELETE', `/v2/domains/${encodeURIComponent(id)}`);\n }\n\n // ── Logs ──\n\n async listEmails(params: ListLogsParams = {}): Promise<Paginated<EmailLog>> {\n return this.request<Paginated<EmailLog>>('GET', '/v2/emails', undefined, params);\n }\n\n /** Look up one message by the id POST /v2/emails returned. */\n async getEmail(messageId: string): Promise<EmailLog> {\n const res = await this.request<{ data: EmailLog }>('GET', `/v2/emails/${encodeURIComponent(messageId)}`);\n return res.data;\n }\n\n // ── Suppressions ──\n\n async listSuppressions(params: { page?: number; per_page?: number; search?: string; status?: string } = {}): Promise<Paginated<Suppression>> {\n return this.request<Paginated<Suppression>>('GET', '/v2/suppressions', undefined, params);\n }\n\n /** Limited to 500 per day per account, shared with the dashboard. */\n async deleteSuppression(email: string): Promise<SuppressionReactivation> {\n return this.request<SuppressionReactivation>('DELETE', '/v2/suppressions', undefined, { email });\n }\n\n // ── Webhooks ──\n\n async listWebhooks(params: { page?: number; per_page?: number } = {}): Promise<Paginated<Webhook>> {\n return this.request<Paginated<Webhook>>('GET', '/v2/webhooks', undefined, params);\n }\n\n /**\n * The signing `secret` is generated by toSend. The API returns it only in\n * this response (the dashboard can show it later), so store it now.\n */\n async createWebhook(params: {\n url: string;\n events: WebhookEvent[];\n title?: string;\n include_message?: boolean;\n }): Promise<Webhook & { secret: string }> {\n const res = await this.request<{ data: Webhook & { secret: string } }>('POST', '/v2/webhooks', params);\n return res.data;\n }\n\n async updateWebhook(id: number, params: Partial<{\n url: string;\n events: WebhookEvent[];\n title: string;\n status: 'active' | 'inactive';\n include_message: boolean;\n }>): Promise<Webhook> {\n const res = await this.request<{ data: Webhook }>('PUT', `/v2/webhooks/${id}`, params);\n return res.data;\n }\n\n async deleteWebhook(id: number): Promise<void> {\n await this.request('DELETE', `/v2/webhooks/${id}`);\n }\n\n // ── Sending keys ──\n\n async listSendingKeys(params: { page?: number; per_page?: number } = {}): Promise<Paginated<SendingKey>> {\n return this.request<Paginated<SendingKey>>('GET', '/v2/api-keys', undefined, params);\n }\n\n /**\n * Mint a sending key for this team — how you give each client their own key.\n *\n * The raw key is returned once and never again. Pass `domain` (a domain id)\n * to restrict it to one domain; omit it for all domains on the team.\n * Needs the `api_keys:write` and `emails:send` scopes.\n */\n async createSendingKey(name: string, domain?: string): Promise<CreatedSendingKey> {\n const res = await this.request<{ data: CreatedSendingKey }>('POST', '/v2/api-keys', { name, domain });\n return res.data;\n }\n\n async deleteSendingKey(id: number): Promise<void> {\n await this.request('DELETE', `/v2/api-keys/${id}`);\n }\n\n // ── Teams ──\n\n async listTeams(): Promise<TeamList> {\n const res = await this.request<{ data: TeamList }>('GET', '/v2/teams');\n return res.data;\n }\n\n /** Only the parent account can create teams. */\n async createTeam(name: string): Promise<{ id: number; name: string; status: string }> {\n const res = await this.request<{ data: any }>('POST', '/v2/teams', { name });\n return res.data;\n }\n\n async updateTeam(id: number, params: { name?: string; status?: 'active' | 'suspended' }): Promise<{ id: number; name: string; status: string }> {\n const res = await this.request<{ data: any }>('PUT', `/v2/teams/${id}`, params);\n return res.data;\n }\n\n // ── Private ──\n\n private async request<T>(\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n endpoint: string,\n body?: unknown,\n query?: Record<string, string | number | undefined> | ListLogsParams | { page?: number; per_page?: number }\n ): Promise<T> {\n let url = `${this.baseUrl}${endpoint}`;\n\n if (query) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query as Record<string, unknown>)) {\n if (value !== undefined && value !== '') params.set(key, String(value));\n }\n const qs = params.toString();\n if (qs) url += `?${qs}`;\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n\n if (this.accountId !== undefined) {\n headers['X-Account-Id'] = String(this.accountId);\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const data = await response.json() as T & {\n message?: string;\n errors?: ValidationErrors;\n error_type?: ErrorType;\n };\n\n if (!response.ok) {\n throw new ToSendError(\n data.message ?? 'Unknown error',\n response.status,\n data.errors ?? {},\n data.error_type\n );\n }\n\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ToSendError) throw error;\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') throw new ToSendError('Request timeout', 408);\n throw new ToSendError(error.message);\n }\n\n throw new ToSendError('Unknown error');\n }\n }\n}\n\n// ============================================================================\n// Default Export\n// ============================================================================\n\nexport default ToSend;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * ToSend Node.js SDK\n *\n * Official SDK for the ToSend email API.\n * @see https://tosend.com/docs\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface Address {\n email: string;\n name?: string;\n}\n\nexport interface Attachment {\n /** MIME type (e.g., 'application/pdf') */\n type: string;\n /** Filename */\n name: string;\n /** Base64 encoded content */\n content: string;\n /** Embeds the file inline: reference it in the HTML as `cid:<content_id>` */\n content_id?: string;\n}\n\nexport interface SendEmailParams {\n /** Sender address */\n from: Address;\n /** Recipients (max 50) */\n to: Address[];\n /** Email subject */\n subject: string;\n /** HTML content */\n html?: string;\n /** Plain text content */\n text?: string;\n /** CC recipients */\n cc?: Address[];\n /** BCC recipients */\n bcc?: Address[];\n /** Reply-to address */\n reply_to?: Address;\n /** Custom headers */\n headers?: Record<string, string>;\n /** File attachments */\n attachments?: Attachment[];\n}\n\nexport interface SendEmailResponse {\n message_id: string;\n}\n\nexport interface BatchEmailResult {\n status: 'success' | 'error' | 'spam';\n message_id?: string;\n message?: string;\n errors?: ValidationErrors;\n}\n\nexport interface BatchEmailResponse {\n results: BatchEmailResult[];\n}\n\nexport interface Domain {\n id: number;\n domain_name: string;\n verification_status: string;\n /** 1 for the account's default domain, else 0 */\n is_default: number;\n total_sent: number;\n total_bounced: number;\n total_complained: number;\n}\n\nexport interface AccountInfo {\n account: {\n id: number;\n status: string;\n plan_type: string;\n /** null means the account does not consume credits */\n credit_balance: number | null;\n monthly_email_limit: number;\n limit_per_second: number;\n ses_region: string;\n /** @deprecated Not returned to a sending key. Use `new ToSendAdmin(key).getInfo()`. */\n bounce_rate?: number;\n /** @deprecated Not returned to a sending key. Use `new ToSendAdmin(key).getInfo()`. */\n complaint_rate?: number;\n ses_status: string;\n };\n domains: Domain[];\n}\n\nexport interface ToSendErrorResponse {\n status_code: number;\n error_type: ErrorType;\n message: string;\n /**\n * Field-level validation detail.\n *\n * The sending API returns a nested map (`{subject: {required: \"...\"}}`);\n * the Worker's ApiError returns a flat one (`{name: \"required\"}`). Both\n * shapes reach this type, so consumers must not assume either — see\n * ToSendError.errors.\n */\n errors?: ValidationErrors;\n}\n\n/**\n * Either shape the API may return for `errors`.\n *\n * ToSendError declared the nested form only, so `err.errors.name` was typed\n * as an object while the Worker actually sends the string `'required'`. That\n * compiled because the response was cast, and would have surfaced as an\n * undefined property at runtime.\n */\nexport type ValidationErrors = Record<string, string | Record<string, string>>;\n\nexport type ErrorType =\n | 'bad_request'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'validation_error'\n | 'rate_limit_exceeded'\n | 'internal_error';\n\nexport interface ToSendConfig {\n /** Your ToSend API key */\n apiKey: string;\n /** Custom base URL (optional) */\n baseUrl?: string;\n /** Request timeout in ms (default: 30000) */\n timeout?: number;\n}\n\n// ============================================================================\n// Error Class\n// ============================================================================\n\nexport class ToSendError extends Error {\n readonly code: number;\n readonly errorType: ErrorType;\n /** See ValidationErrors — the value may be a string or a nested map. */\n readonly errors: ValidationErrors;\n\n private static readonly ERROR_TYPE_MAP: Record<number, ErrorType> = {\n 400: 'bad_request',\n 401: 'unauthorized',\n 403: 'forbidden',\n 404: 'not_found',\n 422: 'validation_error',\n 429: 'rate_limit_exceeded',\n };\n\n constructor(\n message: string,\n code: number = 0,\n errors: ValidationErrors = {},\n errorType?: ErrorType\n ) {\n super(message);\n this.name = 'ToSendError';\n this.code = code;\n this.errors = errors;\n this.errorType = errorType ?? ToSendError.ERROR_TYPE_MAP[code] ?? 'internal_error';\n }\n\n get isValidationError(): boolean {\n return this.errorType === 'validation_error' || this.code === 422;\n }\n\n get isAuthenticationError(): boolean {\n return (\n this.errorType === 'unauthorized' ||\n this.errorType === 'forbidden' ||\n this.code === 401 ||\n this.code === 403\n );\n }\n\n get isRateLimitError(): boolean {\n return this.errorType === 'rate_limit_exceeded' || this.code === 429;\n }\n}\n\n// ============================================================================\n// Main Client\n// ============================================================================\n\nexport class ToSend {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: string | ToSendConfig) {\n if (typeof config === 'string') {\n this.apiKey = config;\n this.baseUrl = 'https://api.tosend.com';\n this.timeout = 30000;\n } else {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl?.replace(/\\/$/, '') ?? 'https://api.tosend.com';\n this.timeout = config.timeout ?? 30000;\n }\n\n if (!this.apiKey?.trim()) {\n throw new ToSendError('API key is required', 401);\n }\n }\n\n /**\n * Send a single email.\n *\n * @example\n * ```ts\n * const response = await tosend.send({\n * from: { email: 'hello@example.com', name: 'My App' },\n * to: [{ email: 'user@example.com' }],\n * subject: 'Hello!',\n * html: '<p>Welcome!</p>'\n * });\n * console.log(response.message_id);\n * ```\n */\n async send(params: SendEmailParams): Promise<SendEmailResponse> {\n this.validateEmailParams(params);\n return this.request<SendEmailResponse>('POST', '/v2/emails', params);\n }\n\n /**\n * Send multiple emails in a single request.\n *\n * @example\n * ```ts\n * const response = await tosend.batch([\n * {\n * from: { email: 'hello@example.com' },\n * to: [{ email: 'user1@example.com' }],\n * subject: 'Hello User 1',\n * html: '<p>Hi!</p>'\n * },\n * {\n * from: { email: 'hello@example.com' },\n * to: [{ email: 'user2@example.com' }],\n * subject: 'Hello User 2',\n * html: '<p>Hi!</p>'\n * }\n * ]);\n *\n * response.results.forEach(result => {\n * if (result.status === 'success') {\n * console.log('Sent:', result.message_id);\n * }\n * });\n * ```\n */\n async batch(emails: SendEmailParams[]): Promise<BatchEmailResponse> {\n if (!emails?.length) {\n throw new ToSendError('Emails array is required and cannot be empty', 422, {\n emails: { required: 'At least one email is required' },\n });\n }\n\n emails.forEach((email, index) => {\n try {\n this.validateEmailParams(email);\n } catch (error) {\n if (error instanceof ToSendError) {\n throw new ToSendError(\n `Email at index ${index}: ${error.message}`,\n error.code,\n error.errors\n );\n }\n throw error;\n }\n });\n\n return this.request<BatchEmailResponse>('POST', '/v2/emails/batch', { emails });\n }\n\n /**\n * Get account information and domains.\n *\n * @example\n * ```ts\n * const info = await tosend.getAccountInfo();\n * console.log(info.account.title);\n * console.log(info.account.emails_usage_this_month);\n * info.domains.forEach(d => console.log(d.domain_name, d.verification_status));\n * ```\n */\n async getAccountInfo(): Promise<AccountInfo> {\n return this.request<AccountInfo>('GET', '/v2/info');\n }\n\n // ==========================================================================\n // Private Methods\n // ==========================================================================\n\n private validateEmailParams(params: SendEmailParams): void {\n const errors: Record<string, Record<string, string>> = {};\n\n if (!params.from?.email) {\n errors.from = { required: 'From email is required' };\n } else if (!this.isValidEmail(params.from.email)) {\n errors.from = { invalid: 'From email is invalid' };\n }\n\n if (!params.to?.length) {\n errors.to = { required: 'To is required' };\n } else {\n const hasValidRecipient = params.to.some(\n (r) => r.email && this.isValidEmail(r.email)\n );\n if (!hasValidRecipient) {\n errors.to = { invalid: 'At least one valid recipient email is required' };\n }\n }\n\n if (!params.subject) {\n errors.subject = { required: 'Subject is required' };\n }\n\n if (!params.html && !params.text) {\n errors.content = { required: 'Either html or text content is required' };\n }\n\n if (Object.keys(errors).length > 0) {\n const firstError = Object.values(errors)[0];\n const message = Object.values(firstError)[0];\n throw new ToSendError(message, 422, errors);\n }\n }\n\n private isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n }\n\n private async request<T>(\n method: 'GET' | 'POST',\n endpoint: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const data = await response.json() as T & {\n message?: string;\n errors?: ValidationErrors;\n error_type?: ErrorType;\n };\n\n if (!response.ok) {\n throw new ToSendError(\n data.message ?? 'Unknown error',\n response.status,\n data.errors ?? {},\n data.error_type\n );\n }\n\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ToSendError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new ToSendError('Request timeout', 408);\n }\n throw new ToSendError(error.message);\n }\n\n throw new ToSendError('Unknown error');\n }\n }\n}\n\n// ============================================================================\n// Admin API Types\n// ============================================================================\n\nexport interface AdminDomain {\n /** Domain identifier used across the Admin API */\n id: string;\n domain_name: string;\n verification_status: 'draft' | 'pending' | 'verified' | 'failed';\n is_default: boolean;\n ses_region: string | null;\n total_sent: number;\n total_bounced: number;\n total_complained: number;\n updated_at: string;\n}\n\nexport interface DnsRecord {\n type: 'TXT' | 'MX';\n name: string;\n content: string;\n TTL: string;\n priority: string;\n /**\n * NOT returned by the Admin API.\n *\n * The domain-detail endpoint does not resolve DNS on your behalf (that\n * would make a GET fire four lookups and a write), so there is no\n * per-record status. Poll `verification_status` on the domain instead —\n * it is the result of the automatic 10-minute check.\n *\n * @deprecated Always undefined. Kept so existing code still compiles.\n */\n status?: string;\n}\n\nexport interface CreatedDomain {\n /** Same identifier the list and detail endpoints use */\n id: string;\n domain_name: string;\n verification_status: string;\n created_at: string;\n}\n\nexport interface AdminDomainDetail {\n id: string;\n domain_name: string;\n verification_status: string;\n dmarc_policy: string | null;\n created_at: string;\n dns_records: {\n dkim: DnsRecord;\n spf: DnsRecord[];\n };\n /** Whether the automatic 10-minute DNS check still covers this domain */\n auto_verification: { active: boolean; reason: string };\n}\n\nexport interface EmailLog {\n /** Same value POST /v2/emails returned as message_id */\n id: string;\n status: string;\n subject: string | null;\n from: string | null;\n from_name: string | null;\n to: Array<{ email: string; name?: string }>;\n reply_to: string | null;\n recipients_count: number;\n error: string | null;\n has_attachments: boolean;\n sent_at: string | null;\n created_at: string;\n}\n\nexport interface Paginated<T> {\n data: T[];\n total: number;\n page: number;\n per_page: number;\n}\n\nexport interface ListLogsParams {\n page?: number;\n /** 1-100, default 25 */\n per_page?: number;\n status?: string;\n /** Domain name or domain id */\n domain?: string;\n from?: string;\n to?: string;\n search?: string;\n}\n\nexport interface Suppression {\n email: string;\n bounce_sub_type: string | null;\n bounce_reason: string | null;\n hard_bounced_at: string | null;\n last_complaint_at: string | null;\n complaint_count: number;\n created_at: string;\n updated_at: string;\n}\n\n/** The only events toSend emits. Delivery, open and click do not exist. */\nexport type WebhookEvent = 'bounced' | 'complaint';\n\nexport interface Webhook {\n id: number;\n title: string | null;\n url: string;\n events: WebhookEvent[];\n status: string;\n include_message: boolean;\n created_at: string;\n updated_at: string;\n}\n\nexport interface SendingKey {\n id: number;\n title: string | null;\n /** Masked. The full key is returned only by createSendingKey(). */\n key: string;\n status: string;\n domain: string | null;\n created_at: string;\n}\n\nexport interface CreatedSendingKey {\n id: number;\n name: string;\n /** Shown once. Store it now — it cannot be retrieved later. */\n api_key: string;\n created_at: string;\n}\n\n/** GET /v2/info with an admin key */\nexport interface AdminInfo {\n account: {\n id: number;\n status: string;\n plan_type: string;\n /** The pooled balance for the whole account, never a child team's own */\n credit_balance: number | null;\n credits_shared: boolean;\n limit_per_second: number;\n ses_region: string;\n /** Last 30 days, fraction between 0 and 1 (0.004 = 0.4%) */\n bounce_rate: number;\n /** Last 30 days, fraction between 0 and 1 */\n complaint_rate: number;\n };\n key: {\n id: number;\n title: string;\n type: 'admin';\n /** The team this request acted on */\n acting_on: number;\n /** The team that owns the key */\n owned_by: number;\n scopes: string[];\n };\n /** Present only when the key has `domains:read` */\n domains?: AdminDomain[];\n}\n\nexport interface SuppressionReactivation {\n deleted: boolean;\n message: string;\n used_today: number;\n remaining_today: number;\n daily_limit: number;\n}\n\n/** Where a team sends from. Fixed for the life of the team. */\nexport type SesRegion = 'us-east-1' | 'eu-central-1';\n\nexport interface TeamList {\n account: { id: number; name: string | null; status: string; credit_balance: number | null };\n teams: Array<{ id: number; name: string | null; status: string; ses_region: SesRegion }>;\n /** Credits pool at the parent; child teams have no separate balance */\n credits_shared: boolean;\n}\n\nexport interface AdminConfig {\n /** Your admin API key (tsend_admin_...) */\n apiKey: string;\n /** Act on a child team by default. Overridable per call. */\n accountId?: number;\n baseUrl?: string;\n timeout?: number;\n}\n\n// ============================================================================\n// Admin Client\n// ============================================================================\n\n/**\n * Account-level API client.\n *\n * Deliberately a separate class from `ToSend`: admin keys and sending keys are\n * different credentials and are not interchangeable. A sending key cannot\n * manage the account, and an admin key can only send when it holds the\n * `emails:send` scope. Keeping them on separate clients means the mistake is\n * caught at the type level rather than by a 401 in production.\n *\n * @example\n * ```ts\n * const admin = new ToSendAdmin(process.env.TOSEND_ADMIN_KEY!);\n *\n * const domain = await admin.createDomain('client.example.com');\n * const detail = await admin.getDomain(domain.id);\n * console.log(detail.dns_records.dkim);\n * // No verify call needed — toSend checks DNS every 10 minutes.\n * ```\n */\nexport class ToSendAdmin {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n private readonly accountId?: number;\n\n constructor(config: string | AdminConfig) {\n if (typeof config === 'string') {\n this.apiKey = config;\n this.baseUrl = 'https://api.tosend.com';\n this.timeout = 30000;\n } else {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl?.replace(/\\/$/, '') ?? 'https://api.tosend.com';\n this.timeout = config.timeout ?? 30000;\n this.accountId = config.accountId;\n }\n\n if (!this.apiKey?.trim()) {\n throw new ToSendError('Admin API key is required', 401);\n }\n\n // Caught here rather than as a 401 from the server, because pasting a\n // sending key is the likeliest mistake and the message should say so.\n if (!this.apiKey.startsWith('tsend_admin_')) {\n throw new ToSendError(\n 'This is not an admin API key. Admin keys start with \"tsend_admin_\" and are created in the dashboard under API Keys.',\n 401\n );\n }\n }\n\n /**\n * The team this key acts on, the key itself and its scopes. Needs no scope,\n * so it is the way to check a key works.\n */\n async getInfo(): Promise<AdminInfo> {\n return this.request<AdminInfo>('GET', '/v2/info');\n }\n\n /** Return a client scoped to one of your child teams. */\n forAccount(accountId: number): ToSendAdmin {\n return new ToSendAdmin({\n apiKey: this.apiKey,\n baseUrl: this.baseUrl,\n timeout: this.timeout,\n accountId,\n });\n }\n\n // ── Sending (requires the emails:send scope) ──\n\n /**\n * Send an email as the targeted team (the key's own, or one set with\n * forAccount()). The key must hold the `emails:send` scope.\n */\n async send(params: SendEmailParams): Promise<SendEmailResponse> {\n return this.request<SendEmailResponse>('POST', '/v2/emails', params);\n }\n\n /** Send up to 100 emails in one request; same scope and targeting as send(). */\n async batch(emails: SendEmailParams[]): Promise<BatchEmailResponse> {\n return this.request<BatchEmailResponse>('POST', '/v2/emails/batch', { emails });\n }\n\n // ── Domains ──\n\n async listDomains(params: { page?: number; per_page?: number } = {}): Promise<Paginated<AdminDomain>> {\n return this.request<Paginated<AdminDomain>>('GET', '/v2/domains', undefined, params);\n }\n\n /**\n * Add a sending domain.\n *\n * The domain starts in `draft`. There is NO verify call to make: toSend\n * checks public DNS every 10 minutes and provisions the domain as soon as\n * the records resolve.\n *\n * A duplicate throws a 409. Treat that as \"already exists, go read it\" —\n * domain names are globally unique, so a retry after a timeout hits this\n * for a domain your first call created successfully.\n */\n async createDomain(name: string): Promise<CreatedDomain> {\n const res = await this.request<{ data: CreatedDomain }>('POST', '/v2/domains', { name });\n return res.data;\n }\n\n /** Domain detail plus the DNS records your customer must publish. */\n async getDomain(id: string): Promise<AdminDomainDetail> {\n const res = await this.request<{ data: AdminDomainDetail }>('GET', `/v2/domains/${encodeURIComponent(id)}`);\n return res.data;\n }\n\n /**\n * Remove a domain.\n *\n * Does NOT remove anything from your customer's DNS zone. Records left\n * published can allow the domain to be re-verified elsewhere, so clean them\n * up in your own workflow.\n */\n async deleteDomain(id: string): Promise<void> {\n await this.request('DELETE', `/v2/domains/${encodeURIComponent(id)}`);\n }\n\n // ── Logs ──\n\n async listEmails(params: ListLogsParams = {}): Promise<Paginated<EmailLog>> {\n return this.request<Paginated<EmailLog>>('GET', '/v2/emails', undefined, params);\n }\n\n /** Look up one message by the id POST /v2/emails returned. */\n async getEmail(messageId: string): Promise<EmailLog> {\n const res = await this.request<{ data: EmailLog }>('GET', `/v2/emails/${encodeURIComponent(messageId)}`);\n return res.data;\n }\n\n // ── Suppressions ──\n\n async listSuppressions(params: { page?: number; per_page?: number; search?: string; status?: string } = {}): Promise<Paginated<Suppression>> {\n return this.request<Paginated<Suppression>>('GET', '/v2/suppressions', undefined, params);\n }\n\n /** Limited to 500 per day per account, shared with the dashboard. */\n async deleteSuppression(email: string): Promise<SuppressionReactivation> {\n return this.request<SuppressionReactivation>('DELETE', '/v2/suppressions', undefined, { email });\n }\n\n // ── Webhooks ──\n\n async listWebhooks(params: { page?: number; per_page?: number } = {}): Promise<Paginated<Webhook>> {\n return this.request<Paginated<Webhook>>('GET', '/v2/webhooks', undefined, params);\n }\n\n /**\n * The signing `secret` is generated by toSend. The API returns it only in\n * this response (the dashboard can show it later), so store it now.\n */\n async createWebhook(params: {\n url: string;\n events: WebhookEvent[];\n title?: string;\n include_message?: boolean;\n }): Promise<Webhook & { secret: string }> {\n const res = await this.request<{ data: Webhook & { secret: string } }>('POST', '/v2/webhooks', params);\n return res.data;\n }\n\n async updateWebhook(id: number, params: Partial<{\n url: string;\n events: WebhookEvent[];\n title: string;\n status: 'active' | 'inactive';\n include_message: boolean;\n }>): Promise<Webhook> {\n const res = await this.request<{ data: Webhook }>('PUT', `/v2/webhooks/${id}`, params);\n return res.data;\n }\n\n async deleteWebhook(id: number): Promise<void> {\n await this.request('DELETE', `/v2/webhooks/${id}`);\n }\n\n // ── Sending keys ──\n\n async listSendingKeys(params: { page?: number; per_page?: number } = {}): Promise<Paginated<SendingKey>> {\n return this.request<Paginated<SendingKey>>('GET', '/v2/api-keys', undefined, params);\n }\n\n /**\n * Mint a sending key for this team — how you give each client their own key.\n *\n * The raw key is returned once and never again. Pass `domain` (a domain id)\n * to restrict it to one domain; omit it for all domains on the team.\n * Needs the `api_keys:write` and `emails:send` scopes.\n */\n async createSendingKey(name: string, domain?: string): Promise<CreatedSendingKey> {\n const res = await this.request<{ data: CreatedSendingKey }>('POST', '/v2/api-keys', { name, domain });\n return res.data;\n }\n\n async deleteSendingKey(id: number): Promise<void> {\n await this.request('DELETE', `/v2/api-keys/${id}`);\n }\n\n // ── Teams ──\n\n async listTeams(): Promise<TeamList> {\n const res = await this.request<{ data: TeamList }>('GET', '/v2/teams');\n return res.data;\n }\n\n /**\n * Only the parent account can create teams. `ses_region` defaults to\n * 'us-east-1' and cannot be changed once the team exists.\n */\n async createTeam(\n name: string,\n options: { ses_region?: SesRegion } = {},\n ): Promise<{ id: number; name: string; status: string; ses_region: SesRegion }> {\n const body: Record<string, unknown> = { name };\n if (options.ses_region) body.ses_region = options.ses_region;\n const res = await this.request<{ data: any }>('POST', '/v2/teams', body);\n return res.data;\n }\n\n async updateTeam(id: number, params: { name?: string; status?: 'active' | 'suspended' }): Promise<{ id: number; name: string; status: string }> {\n const res = await this.request<{ data: any }>('PUT', `/v2/teams/${id}`, params);\n return res.data;\n }\n\n // ── Private ──\n\n private async request<T>(\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n endpoint: string,\n body?: unknown,\n query?: Record<string, string | number | undefined> | ListLogsParams | { page?: number; per_page?: number }\n ): Promise<T> {\n let url = `${this.baseUrl}${endpoint}`;\n\n if (query) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query as Record<string, unknown>)) {\n if (value !== undefined && value !== '') params.set(key, String(value));\n }\n const qs = params.toString();\n if (qs) url += `?${qs}`;\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n\n if (this.accountId !== undefined) {\n headers['X-Account-Id'] = String(this.accountId);\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const data = await response.json() as T & {\n message?: string;\n errors?: ValidationErrors;\n error_type?: ErrorType;\n };\n\n if (!response.ok) {\n throw new ToSendError(\n data.message ?? 'Unknown error',\n response.status,\n data.errors ?? {},\n data.error_type\n );\n }\n\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ToSendError) throw error;\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') throw new ToSendError('Request timeout', 408);\n throw new ToSendError(error.message);\n }\n\n throw new ToSendError('Unknown error');\n }\n }\n}\n\n// ============================================================================\n// Default Export\n// ============================================================================\n\nexport default ToSend;\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -351,9 +351,14 @@ var ToSendAdmin = class _ToSendAdmin {
351
351
  const res = await this.request("GET", "/v2/teams");
352
352
  return res.data;
353
353
  }
354
- /** Only the parent account can create teams. */
355
- async createTeam(name) {
356
- const res = await this.request("POST", "/v2/teams", { name });
354
+ /**
355
+ * Only the parent account can create teams. `ses_region` defaults to
356
+ * 'us-east-1' and cannot be changed once the team exists.
357
+ */
358
+ async createTeam(name, options = {}) {
359
+ const body = { name };
360
+ if (options.ses_region) body.ses_region = options.ses_region;
361
+ const res = await this.request("POST", "/v2/teams", body);
357
362
  return res.data;
358
363
  }
359
364
  async updateTeam(id, params) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * ToSend Node.js SDK\n *\n * Official SDK for the ToSend email API.\n * @see https://tosend.com/docs\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface Address {\n email: string;\n name?: string;\n}\n\nexport interface Attachment {\n /** MIME type (e.g., 'application/pdf') */\n type: string;\n /** Filename */\n name: string;\n /** Base64 encoded content */\n content: string;\n}\n\nexport interface SendEmailParams {\n /** Sender address */\n from: Address;\n /** Recipients (max 50) */\n to: Address[];\n /** Email subject */\n subject: string;\n /** HTML content */\n html?: string;\n /** Plain text content */\n text?: string;\n /** CC recipients */\n cc?: Address[];\n /** BCC recipients */\n bcc?: Address[];\n /** Reply-to address */\n reply_to?: Address;\n /** Custom headers */\n headers?: Record<string, string>;\n /** File attachments */\n attachments?: Attachment[];\n}\n\nexport interface SendEmailResponse {\n message_id: string;\n}\n\nexport interface BatchEmailResult {\n status: 'success' | 'error' | 'spam';\n message_id?: string;\n message?: string;\n errors?: ValidationErrors;\n}\n\nexport interface BatchEmailResponse {\n results: BatchEmailResult[];\n}\n\nexport interface Domain {\n id: number;\n domain_name: string;\n verification_status: string;\n /** 1 for the account's default domain, else 0 */\n is_default: number;\n total_sent: number;\n total_bounced: number;\n total_complained: number;\n}\n\nexport interface AccountInfo {\n account: {\n id: number;\n status: string;\n plan_type: string;\n /** null means the account does not consume credits */\n credit_balance: number | null;\n monthly_email_limit: number;\n limit_per_second: number;\n ses_region: string;\n /** Fraction between 0 and 1, not a percentage */\n bounce_rate: number;\n /** Fraction between 0 and 1, not a percentage */\n complaint_rate: number;\n ses_status: string;\n };\n domains: Domain[];\n}\n\nexport interface ToSendErrorResponse {\n status_code: number;\n error_type: ErrorType;\n message: string;\n /**\n * Field-level validation detail.\n *\n * The sending API returns a nested map (`{subject: {required: \"...\"}}`);\n * the Worker's ApiError returns a flat one (`{name: \"required\"}`). Both\n * shapes reach this type, so consumers must not assume either — see\n * ToSendError.errors.\n */\n errors?: ValidationErrors;\n}\n\n/**\n * Either shape the API may return for `errors`.\n *\n * ToSendError declared the nested form only, so `err.errors.name` was typed\n * as an object while the Worker actually sends the string `'required'`. That\n * compiled because the response was cast, and would have surfaced as an\n * undefined property at runtime.\n */\nexport type ValidationErrors = Record<string, string | Record<string, string>>;\n\nexport type ErrorType =\n | 'bad_request'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'validation_error'\n | 'rate_limit_exceeded'\n | 'internal_error';\n\nexport interface ToSendConfig {\n /** Your ToSend API key */\n apiKey: string;\n /** Custom base URL (optional) */\n baseUrl?: string;\n /** Request timeout in ms (default: 30000) */\n timeout?: number;\n}\n\n// ============================================================================\n// Error Class\n// ============================================================================\n\nexport class ToSendError extends Error {\n readonly code: number;\n readonly errorType: ErrorType;\n /** See ValidationErrors — the value may be a string or a nested map. */\n readonly errors: ValidationErrors;\n\n private static readonly ERROR_TYPE_MAP: Record<number, ErrorType> = {\n 400: 'bad_request',\n 401: 'unauthorized',\n 403: 'forbidden',\n 404: 'not_found',\n 422: 'validation_error',\n 429: 'rate_limit_exceeded',\n };\n\n constructor(\n message: string,\n code: number = 0,\n errors: ValidationErrors = {},\n errorType?: ErrorType\n ) {\n super(message);\n this.name = 'ToSendError';\n this.code = code;\n this.errors = errors;\n this.errorType = errorType ?? ToSendError.ERROR_TYPE_MAP[code] ?? 'internal_error';\n }\n\n get isValidationError(): boolean {\n return this.errorType === 'validation_error' || this.code === 422;\n }\n\n get isAuthenticationError(): boolean {\n return (\n this.errorType === 'unauthorized' ||\n this.errorType === 'forbidden' ||\n this.code === 401 ||\n this.code === 403\n );\n }\n\n get isRateLimitError(): boolean {\n return this.errorType === 'rate_limit_exceeded' || this.code === 429;\n }\n}\n\n// ============================================================================\n// Main Client\n// ============================================================================\n\nexport class ToSend {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: string | ToSendConfig) {\n if (typeof config === 'string') {\n this.apiKey = config;\n this.baseUrl = 'https://api.tosend.com';\n this.timeout = 30000;\n } else {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl?.replace(/\\/$/, '') ?? 'https://api.tosend.com';\n this.timeout = config.timeout ?? 30000;\n }\n\n if (!this.apiKey?.trim()) {\n throw new ToSendError('API key is required', 401);\n }\n }\n\n /**\n * Send a single email.\n *\n * @example\n * ```ts\n * const response = await tosend.send({\n * from: { email: 'hello@example.com', name: 'My App' },\n * to: [{ email: 'user@example.com' }],\n * subject: 'Hello!',\n * html: '<p>Welcome!</p>'\n * });\n * console.log(response.message_id);\n * ```\n */\n async send(params: SendEmailParams): Promise<SendEmailResponse> {\n this.validateEmailParams(params);\n return this.request<SendEmailResponse>('POST', '/v2/emails', params);\n }\n\n /**\n * Send multiple emails in a single request.\n *\n * @example\n * ```ts\n * const response = await tosend.batch([\n * {\n * from: { email: 'hello@example.com' },\n * to: [{ email: 'user1@example.com' }],\n * subject: 'Hello User 1',\n * html: '<p>Hi!</p>'\n * },\n * {\n * from: { email: 'hello@example.com' },\n * to: [{ email: 'user2@example.com' }],\n * subject: 'Hello User 2',\n * html: '<p>Hi!</p>'\n * }\n * ]);\n *\n * response.results.forEach(result => {\n * if (result.status === 'success') {\n * console.log('Sent:', result.message_id);\n * }\n * });\n * ```\n */\n async batch(emails: SendEmailParams[]): Promise<BatchEmailResponse> {\n if (!emails?.length) {\n throw new ToSendError('Emails array is required and cannot be empty', 422, {\n emails: { required: 'At least one email is required' },\n });\n }\n\n emails.forEach((email, index) => {\n try {\n this.validateEmailParams(email);\n } catch (error) {\n if (error instanceof ToSendError) {\n throw new ToSendError(\n `Email at index ${index}: ${error.message}`,\n error.code,\n error.errors\n );\n }\n throw error;\n }\n });\n\n return this.request<BatchEmailResponse>('POST', '/v2/emails/batch', { emails });\n }\n\n /**\n * Get account information and domains.\n *\n * @example\n * ```ts\n * const info = await tosend.getAccountInfo();\n * console.log(info.account.title);\n * console.log(info.account.emails_usage_this_month);\n * info.domains.forEach(d => console.log(d.domain_name, d.verification_status));\n * ```\n */\n async getAccountInfo(): Promise<AccountInfo> {\n return this.request<AccountInfo>('GET', '/v2/info');\n }\n\n // ==========================================================================\n // Private Methods\n // ==========================================================================\n\n private validateEmailParams(params: SendEmailParams): void {\n const errors: Record<string, Record<string, string>> = {};\n\n if (!params.from?.email) {\n errors.from = { required: 'From email is required' };\n } else if (!this.isValidEmail(params.from.email)) {\n errors.from = { invalid: 'From email is invalid' };\n }\n\n if (!params.to?.length) {\n errors.to = { required: 'To is required' };\n } else {\n const hasValidRecipient = params.to.some(\n (r) => r.email && this.isValidEmail(r.email)\n );\n if (!hasValidRecipient) {\n errors.to = { invalid: 'At least one valid recipient email is required' };\n }\n }\n\n if (!params.subject) {\n errors.subject = { required: 'Subject is required' };\n }\n\n if (!params.html && !params.text) {\n errors.content = { required: 'Either html or text content is required' };\n }\n\n if (Object.keys(errors).length > 0) {\n const firstError = Object.values(errors)[0];\n const message = Object.values(firstError)[0];\n throw new ToSendError(message, 422, errors);\n }\n }\n\n private isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n }\n\n private async request<T>(\n method: 'GET' | 'POST',\n endpoint: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const data = await response.json() as T & {\n message?: string;\n errors?: ValidationErrors;\n error_type?: ErrorType;\n };\n\n if (!response.ok) {\n throw new ToSendError(\n data.message ?? 'Unknown error',\n response.status,\n data.errors ?? {},\n data.error_type\n );\n }\n\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ToSendError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new ToSendError('Request timeout', 408);\n }\n throw new ToSendError(error.message);\n }\n\n throw new ToSendError('Unknown error');\n }\n }\n}\n\n// ============================================================================\n// Admin API Types\n// ============================================================================\n\nexport interface AdminDomain {\n /** Domain identifier used across the Admin API */\n id: string;\n domain_name: string;\n verification_status: 'draft' | 'pending' | 'verified' | 'failed';\n is_default: boolean;\n ses_region: string | null;\n total_sent: number;\n total_bounced: number;\n total_complained: number;\n updated_at: string;\n}\n\nexport interface DnsRecord {\n type: 'TXT' | 'MX';\n name: string;\n content: string;\n TTL: string;\n priority: string;\n /**\n * NOT returned by the Admin API.\n *\n * The domain-detail endpoint does not resolve DNS on your behalf (that\n * would make a GET fire four lookups and a write), so there is no\n * per-record status. Poll `verification_status` on the domain instead —\n * it is the result of the automatic 10-minute check.\n *\n * @deprecated Always undefined. Kept so existing code still compiles.\n */\n status?: string;\n}\n\nexport interface CreatedDomain {\n /** Same identifier the list and detail endpoints use */\n id: string;\n domain_name: string;\n verification_status: string;\n created_at: string;\n}\n\nexport interface AdminDomainDetail {\n id: string;\n domain_name: string;\n verification_status: string;\n dmarc_policy: string | null;\n created_at: string;\n dns_records: {\n dkim: DnsRecord;\n spf: DnsRecord[];\n };\n /** Whether the automatic 10-minute DNS check still covers this domain */\n auto_verification: { active: boolean; reason: string };\n}\n\nexport interface EmailLog {\n /** Same value POST /v2/emails returned as message_id */\n id: string;\n status: string;\n subject: string | null;\n from: string | null;\n from_name: string | null;\n to: Array<{ email: string; name?: string }>;\n reply_to: string | null;\n recipients_count: number;\n error: string | null;\n has_attachments: boolean;\n sent_at: string | null;\n created_at: string;\n}\n\nexport interface Paginated<T> {\n data: T[];\n total: number;\n page: number;\n per_page: number;\n}\n\nexport interface ListLogsParams {\n page?: number;\n /** 1-100, default 25 */\n per_page?: number;\n status?: string;\n /** Domain name or domain id */\n domain?: string;\n from?: string;\n to?: string;\n search?: string;\n}\n\nexport interface Suppression {\n email: string;\n bounce_sub_type: string | null;\n bounce_reason: string | null;\n hard_bounced_at: string | null;\n last_complaint_at: string | null;\n complaint_count: number;\n created_at: string;\n updated_at: string;\n}\n\n/** The only events toSend emits. Delivery, open and click do not exist. */\nexport type WebhookEvent = 'bounced' | 'complaint';\n\nexport interface Webhook {\n id: number;\n title: string | null;\n url: string;\n events: WebhookEvent[];\n status: string;\n include_message: boolean;\n created_at: string;\n updated_at: string;\n}\n\nexport interface SendingKey {\n id: number;\n title: string | null;\n /** Masked. The full key is returned only by createSendingKey(). */\n key: string;\n status: string;\n domain: string | null;\n created_at: string;\n}\n\nexport interface CreatedSendingKey {\n id: number;\n name: string;\n /** Shown once. Store it now — it cannot be retrieved later. */\n api_key: string;\n created_at: string;\n}\n\n/** GET /v2/info with an admin key */\nexport interface AdminInfo {\n account: {\n id: number;\n status: string;\n plan_type: string;\n /** The pooled balance for the whole account, never a child team's own */\n credit_balance: number | null;\n credits_shared: boolean;\n limit_per_second: number;\n ses_region: string;\n bounce_rate: number;\n complaint_rate: number;\n };\n key: {\n id: number;\n title: string;\n type: 'admin';\n /** The team this request acted on */\n acting_on: number;\n /** The team that owns the key */\n owned_by: number;\n scopes: string[];\n };\n /** Present only when the key has `domains:read` */\n domains?: AdminDomain[];\n}\n\nexport interface SuppressionReactivation {\n deleted: boolean;\n message: string;\n used_today: number;\n remaining_today: number;\n daily_limit: number;\n}\n\nexport interface TeamList {\n account: { id: number; name: string | null; status: string; credit_balance: number | null };\n teams: Array<{ id: number; name: string | null; status: string }>;\n /** Credits pool at the parent; child teams have no separate balance */\n credits_shared: boolean;\n}\n\nexport interface AdminConfig {\n /** Your admin API key (tsend_admin_...) */\n apiKey: string;\n /** Act on a child team by default. Overridable per call. */\n accountId?: number;\n baseUrl?: string;\n timeout?: number;\n}\n\n// ============================================================================\n// Admin Client\n// ============================================================================\n\n/**\n * Account-level API client.\n *\n * Deliberately a separate class from `ToSend`: admin keys and sending keys are\n * different credentials and are not interchangeable. A sending key cannot\n * manage the account, and an admin key can only send when it holds the\n * `emails:send` scope. Keeping them on separate clients means the mistake is\n * caught at the type level rather than by a 401 in production.\n *\n * @example\n * ```ts\n * const admin = new ToSendAdmin(process.env.TOSEND_ADMIN_KEY!);\n *\n * const domain = await admin.createDomain('client.example.com');\n * const detail = await admin.getDomain(domain.id);\n * console.log(detail.dns_records.dkim);\n * // No verify call needed — toSend checks DNS every 10 minutes.\n * ```\n */\nexport class ToSendAdmin {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n private readonly accountId?: number;\n\n constructor(config: string | AdminConfig) {\n if (typeof config === 'string') {\n this.apiKey = config;\n this.baseUrl = 'https://api.tosend.com';\n this.timeout = 30000;\n } else {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl?.replace(/\\/$/, '') ?? 'https://api.tosend.com';\n this.timeout = config.timeout ?? 30000;\n this.accountId = config.accountId;\n }\n\n if (!this.apiKey?.trim()) {\n throw new ToSendError('Admin API key is required', 401);\n }\n\n // Caught here rather than as a 401 from the server, because pasting a\n // sending key is the likeliest mistake and the message should say so.\n if (!this.apiKey.startsWith('tsend_admin_')) {\n throw new ToSendError(\n 'This is not an admin API key. Admin keys start with \"tsend_admin_\" and are created in the dashboard under API Keys.',\n 401\n );\n }\n }\n\n /**\n * The team this key acts on, the key itself and its scopes. Needs no scope,\n * so it is the way to check a key works.\n */\n async getInfo(): Promise<AdminInfo> {\n return this.request<AdminInfo>('GET', '/v2/info');\n }\n\n /** Return a client scoped to one of your child teams. */\n forAccount(accountId: number): ToSendAdmin {\n return new ToSendAdmin({\n apiKey: this.apiKey,\n baseUrl: this.baseUrl,\n timeout: this.timeout,\n accountId,\n });\n }\n\n // ── Sending (requires the emails:send scope) ──\n\n /**\n * Send an email as the targeted team (the key's own, or one set with\n * forAccount()). The key must hold the `emails:send` scope.\n */\n async send(params: SendEmailParams): Promise<SendEmailResponse> {\n return this.request<SendEmailResponse>('POST', '/v2/emails', params);\n }\n\n /** Send up to 100 emails in one request; same scope and targeting as send(). */\n async batch(emails: SendEmailParams[]): Promise<BatchEmailResponse> {\n return this.request<BatchEmailResponse>('POST', '/v2/emails/batch', { emails });\n }\n\n // ── Domains ──\n\n async listDomains(params: { page?: number; per_page?: number } = {}): Promise<Paginated<AdminDomain>> {\n return this.request<Paginated<AdminDomain>>('GET', '/v2/domains', undefined, params);\n }\n\n /**\n * Add a sending domain.\n *\n * The domain starts in `draft`. There is NO verify call to make: toSend\n * checks public DNS every 10 minutes and provisions the domain as soon as\n * the records resolve.\n *\n * A duplicate throws a 409. Treat that as \"already exists, go read it\" —\n * domain names are globally unique, so a retry after a timeout hits this\n * for a domain your first call created successfully.\n */\n async createDomain(name: string): Promise<CreatedDomain> {\n const res = await this.request<{ data: CreatedDomain }>('POST', '/v2/domains', { name });\n return res.data;\n }\n\n /** Domain detail plus the DNS records your customer must publish. */\n async getDomain(id: string): Promise<AdminDomainDetail> {\n const res = await this.request<{ data: AdminDomainDetail }>('GET', `/v2/domains/${encodeURIComponent(id)}`);\n return res.data;\n }\n\n /**\n * Remove a domain.\n *\n * Does NOT remove anything from your customer's DNS zone. Records left\n * published can allow the domain to be re-verified elsewhere, so clean them\n * up in your own workflow.\n */\n async deleteDomain(id: string): Promise<void> {\n await this.request('DELETE', `/v2/domains/${encodeURIComponent(id)}`);\n }\n\n // ── Logs ──\n\n async listEmails(params: ListLogsParams = {}): Promise<Paginated<EmailLog>> {\n return this.request<Paginated<EmailLog>>('GET', '/v2/emails', undefined, params);\n }\n\n /** Look up one message by the id POST /v2/emails returned. */\n async getEmail(messageId: string): Promise<EmailLog> {\n const res = await this.request<{ data: EmailLog }>('GET', `/v2/emails/${encodeURIComponent(messageId)}`);\n return res.data;\n }\n\n // ── Suppressions ──\n\n async listSuppressions(params: { page?: number; per_page?: number; search?: string; status?: string } = {}): Promise<Paginated<Suppression>> {\n return this.request<Paginated<Suppression>>('GET', '/v2/suppressions', undefined, params);\n }\n\n /** Limited to 500 per day per account, shared with the dashboard. */\n async deleteSuppression(email: string): Promise<SuppressionReactivation> {\n return this.request<SuppressionReactivation>('DELETE', '/v2/suppressions', undefined, { email });\n }\n\n // ── Webhooks ──\n\n async listWebhooks(params: { page?: number; per_page?: number } = {}): Promise<Paginated<Webhook>> {\n return this.request<Paginated<Webhook>>('GET', '/v2/webhooks', undefined, params);\n }\n\n /**\n * The signing `secret` is generated by toSend. The API returns it only in\n * this response (the dashboard can show it later), so store it now.\n */\n async createWebhook(params: {\n url: string;\n events: WebhookEvent[];\n title?: string;\n include_message?: boolean;\n }): Promise<Webhook & { secret: string }> {\n const res = await this.request<{ data: Webhook & { secret: string } }>('POST', '/v2/webhooks', params);\n return res.data;\n }\n\n async updateWebhook(id: number, params: Partial<{\n url: string;\n events: WebhookEvent[];\n title: string;\n status: 'active' | 'inactive';\n include_message: boolean;\n }>): Promise<Webhook> {\n const res = await this.request<{ data: Webhook }>('PUT', `/v2/webhooks/${id}`, params);\n return res.data;\n }\n\n async deleteWebhook(id: number): Promise<void> {\n await this.request('DELETE', `/v2/webhooks/${id}`);\n }\n\n // ── Sending keys ──\n\n async listSendingKeys(params: { page?: number; per_page?: number } = {}): Promise<Paginated<SendingKey>> {\n return this.request<Paginated<SendingKey>>('GET', '/v2/api-keys', undefined, params);\n }\n\n /**\n * Mint a sending key for this team — how you give each client their own key.\n *\n * The raw key is returned once and never again. Pass `domain` (a domain id)\n * to restrict it to one domain; omit it for all domains on the team.\n * Needs the `api_keys:write` and `emails:send` scopes.\n */\n async createSendingKey(name: string, domain?: string): Promise<CreatedSendingKey> {\n const res = await this.request<{ data: CreatedSendingKey }>('POST', '/v2/api-keys', { name, domain });\n return res.data;\n }\n\n async deleteSendingKey(id: number): Promise<void> {\n await this.request('DELETE', `/v2/api-keys/${id}`);\n }\n\n // ── Teams ──\n\n async listTeams(): Promise<TeamList> {\n const res = await this.request<{ data: TeamList }>('GET', '/v2/teams');\n return res.data;\n }\n\n /** Only the parent account can create teams. */\n async createTeam(name: string): Promise<{ id: number; name: string; status: string }> {\n const res = await this.request<{ data: any }>('POST', '/v2/teams', { name });\n return res.data;\n }\n\n async updateTeam(id: number, params: { name?: string; status?: 'active' | 'suspended' }): Promise<{ id: number; name: string; status: string }> {\n const res = await this.request<{ data: any }>('PUT', `/v2/teams/${id}`, params);\n return res.data;\n }\n\n // ── Private ──\n\n private async request<T>(\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n endpoint: string,\n body?: unknown,\n query?: Record<string, string | number | undefined> | ListLogsParams | { page?: number; per_page?: number }\n ): Promise<T> {\n let url = `${this.baseUrl}${endpoint}`;\n\n if (query) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query as Record<string, unknown>)) {\n if (value !== undefined && value !== '') params.set(key, String(value));\n }\n const qs = params.toString();\n if (qs) url += `?${qs}`;\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n\n if (this.accountId !== undefined) {\n headers['X-Account-Id'] = String(this.accountId);\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const data = await response.json() as T & {\n message?: string;\n errors?: ValidationErrors;\n error_type?: ErrorType;\n };\n\n if (!response.ok) {\n throw new ToSendError(\n data.message ?? 'Unknown error',\n response.status,\n data.errors ?? {},\n data.error_type\n );\n }\n\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ToSendError) throw error;\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') throw new ToSendError('Request timeout', 408);\n throw new ToSendError(error.message);\n }\n\n throw new ToSendError('Unknown error');\n }\n }\n}\n\n// ============================================================================\n// Default Export\n// ============================================================================\n\nexport default ToSend;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * ToSend Node.js SDK\n *\n * Official SDK for the ToSend email API.\n * @see https://tosend.com/docs\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface Address {\n email: string;\n name?: string;\n}\n\nexport interface Attachment {\n /** MIME type (e.g., 'application/pdf') */\n type: string;\n /** Filename */\n name: string;\n /** Base64 encoded content */\n content: string;\n /** Embeds the file inline: reference it in the HTML as `cid:<content_id>` */\n content_id?: string;\n}\n\nexport interface SendEmailParams {\n /** Sender address */\n from: Address;\n /** Recipients (max 50) */\n to: Address[];\n /** Email subject */\n subject: string;\n /** HTML content */\n html?: string;\n /** Plain text content */\n text?: string;\n /** CC recipients */\n cc?: Address[];\n /** BCC recipients */\n bcc?: Address[];\n /** Reply-to address */\n reply_to?: Address;\n /** Custom headers */\n headers?: Record<string, string>;\n /** File attachments */\n attachments?: Attachment[];\n}\n\nexport interface SendEmailResponse {\n message_id: string;\n}\n\nexport interface BatchEmailResult {\n status: 'success' | 'error' | 'spam';\n message_id?: string;\n message?: string;\n errors?: ValidationErrors;\n}\n\nexport interface BatchEmailResponse {\n results: BatchEmailResult[];\n}\n\nexport interface Domain {\n id: number;\n domain_name: string;\n verification_status: string;\n /** 1 for the account's default domain, else 0 */\n is_default: number;\n total_sent: number;\n total_bounced: number;\n total_complained: number;\n}\n\nexport interface AccountInfo {\n account: {\n id: number;\n status: string;\n plan_type: string;\n /** null means the account does not consume credits */\n credit_balance: number | null;\n monthly_email_limit: number;\n limit_per_second: number;\n ses_region: string;\n /** @deprecated Not returned to a sending key. Use `new ToSendAdmin(key).getInfo()`. */\n bounce_rate?: number;\n /** @deprecated Not returned to a sending key. Use `new ToSendAdmin(key).getInfo()`. */\n complaint_rate?: number;\n ses_status: string;\n };\n domains: Domain[];\n}\n\nexport interface ToSendErrorResponse {\n status_code: number;\n error_type: ErrorType;\n message: string;\n /**\n * Field-level validation detail.\n *\n * The sending API returns a nested map (`{subject: {required: \"...\"}}`);\n * the Worker's ApiError returns a flat one (`{name: \"required\"}`). Both\n * shapes reach this type, so consumers must not assume either — see\n * ToSendError.errors.\n */\n errors?: ValidationErrors;\n}\n\n/**\n * Either shape the API may return for `errors`.\n *\n * ToSendError declared the nested form only, so `err.errors.name` was typed\n * as an object while the Worker actually sends the string `'required'`. That\n * compiled because the response was cast, and would have surfaced as an\n * undefined property at runtime.\n */\nexport type ValidationErrors = Record<string, string | Record<string, string>>;\n\nexport type ErrorType =\n | 'bad_request'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'validation_error'\n | 'rate_limit_exceeded'\n | 'internal_error';\n\nexport interface ToSendConfig {\n /** Your ToSend API key */\n apiKey: string;\n /** Custom base URL (optional) */\n baseUrl?: string;\n /** Request timeout in ms (default: 30000) */\n timeout?: number;\n}\n\n// ============================================================================\n// Error Class\n// ============================================================================\n\nexport class ToSendError extends Error {\n readonly code: number;\n readonly errorType: ErrorType;\n /** See ValidationErrors — the value may be a string or a nested map. */\n readonly errors: ValidationErrors;\n\n private static readonly ERROR_TYPE_MAP: Record<number, ErrorType> = {\n 400: 'bad_request',\n 401: 'unauthorized',\n 403: 'forbidden',\n 404: 'not_found',\n 422: 'validation_error',\n 429: 'rate_limit_exceeded',\n };\n\n constructor(\n message: string,\n code: number = 0,\n errors: ValidationErrors = {},\n errorType?: ErrorType\n ) {\n super(message);\n this.name = 'ToSendError';\n this.code = code;\n this.errors = errors;\n this.errorType = errorType ?? ToSendError.ERROR_TYPE_MAP[code] ?? 'internal_error';\n }\n\n get isValidationError(): boolean {\n return this.errorType === 'validation_error' || this.code === 422;\n }\n\n get isAuthenticationError(): boolean {\n return (\n this.errorType === 'unauthorized' ||\n this.errorType === 'forbidden' ||\n this.code === 401 ||\n this.code === 403\n );\n }\n\n get isRateLimitError(): boolean {\n return this.errorType === 'rate_limit_exceeded' || this.code === 429;\n }\n}\n\n// ============================================================================\n// Main Client\n// ============================================================================\n\nexport class ToSend {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(config: string | ToSendConfig) {\n if (typeof config === 'string') {\n this.apiKey = config;\n this.baseUrl = 'https://api.tosend.com';\n this.timeout = 30000;\n } else {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl?.replace(/\\/$/, '') ?? 'https://api.tosend.com';\n this.timeout = config.timeout ?? 30000;\n }\n\n if (!this.apiKey?.trim()) {\n throw new ToSendError('API key is required', 401);\n }\n }\n\n /**\n * Send a single email.\n *\n * @example\n * ```ts\n * const response = await tosend.send({\n * from: { email: 'hello@example.com', name: 'My App' },\n * to: [{ email: 'user@example.com' }],\n * subject: 'Hello!',\n * html: '<p>Welcome!</p>'\n * });\n * console.log(response.message_id);\n * ```\n */\n async send(params: SendEmailParams): Promise<SendEmailResponse> {\n this.validateEmailParams(params);\n return this.request<SendEmailResponse>('POST', '/v2/emails', params);\n }\n\n /**\n * Send multiple emails in a single request.\n *\n * @example\n * ```ts\n * const response = await tosend.batch([\n * {\n * from: { email: 'hello@example.com' },\n * to: [{ email: 'user1@example.com' }],\n * subject: 'Hello User 1',\n * html: '<p>Hi!</p>'\n * },\n * {\n * from: { email: 'hello@example.com' },\n * to: [{ email: 'user2@example.com' }],\n * subject: 'Hello User 2',\n * html: '<p>Hi!</p>'\n * }\n * ]);\n *\n * response.results.forEach(result => {\n * if (result.status === 'success') {\n * console.log('Sent:', result.message_id);\n * }\n * });\n * ```\n */\n async batch(emails: SendEmailParams[]): Promise<BatchEmailResponse> {\n if (!emails?.length) {\n throw new ToSendError('Emails array is required and cannot be empty', 422, {\n emails: { required: 'At least one email is required' },\n });\n }\n\n emails.forEach((email, index) => {\n try {\n this.validateEmailParams(email);\n } catch (error) {\n if (error instanceof ToSendError) {\n throw new ToSendError(\n `Email at index ${index}: ${error.message}`,\n error.code,\n error.errors\n );\n }\n throw error;\n }\n });\n\n return this.request<BatchEmailResponse>('POST', '/v2/emails/batch', { emails });\n }\n\n /**\n * Get account information and domains.\n *\n * @example\n * ```ts\n * const info = await tosend.getAccountInfo();\n * console.log(info.account.title);\n * console.log(info.account.emails_usage_this_month);\n * info.domains.forEach(d => console.log(d.domain_name, d.verification_status));\n * ```\n */\n async getAccountInfo(): Promise<AccountInfo> {\n return this.request<AccountInfo>('GET', '/v2/info');\n }\n\n // ==========================================================================\n // Private Methods\n // ==========================================================================\n\n private validateEmailParams(params: SendEmailParams): void {\n const errors: Record<string, Record<string, string>> = {};\n\n if (!params.from?.email) {\n errors.from = { required: 'From email is required' };\n } else if (!this.isValidEmail(params.from.email)) {\n errors.from = { invalid: 'From email is invalid' };\n }\n\n if (!params.to?.length) {\n errors.to = { required: 'To is required' };\n } else {\n const hasValidRecipient = params.to.some(\n (r) => r.email && this.isValidEmail(r.email)\n );\n if (!hasValidRecipient) {\n errors.to = { invalid: 'At least one valid recipient email is required' };\n }\n }\n\n if (!params.subject) {\n errors.subject = { required: 'Subject is required' };\n }\n\n if (!params.html && !params.text) {\n errors.content = { required: 'Either html or text content is required' };\n }\n\n if (Object.keys(errors).length > 0) {\n const firstError = Object.values(errors)[0];\n const message = Object.values(firstError)[0];\n throw new ToSendError(message, 422, errors);\n }\n }\n\n private isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n }\n\n private async request<T>(\n method: 'GET' | 'POST',\n endpoint: string,\n body?: unknown\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const data = await response.json() as T & {\n message?: string;\n errors?: ValidationErrors;\n error_type?: ErrorType;\n };\n\n if (!response.ok) {\n throw new ToSendError(\n data.message ?? 'Unknown error',\n response.status,\n data.errors ?? {},\n data.error_type\n );\n }\n\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ToSendError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new ToSendError('Request timeout', 408);\n }\n throw new ToSendError(error.message);\n }\n\n throw new ToSendError('Unknown error');\n }\n }\n}\n\n// ============================================================================\n// Admin API Types\n// ============================================================================\n\nexport interface AdminDomain {\n /** Domain identifier used across the Admin API */\n id: string;\n domain_name: string;\n verification_status: 'draft' | 'pending' | 'verified' | 'failed';\n is_default: boolean;\n ses_region: string | null;\n total_sent: number;\n total_bounced: number;\n total_complained: number;\n updated_at: string;\n}\n\nexport interface DnsRecord {\n type: 'TXT' | 'MX';\n name: string;\n content: string;\n TTL: string;\n priority: string;\n /**\n * NOT returned by the Admin API.\n *\n * The domain-detail endpoint does not resolve DNS on your behalf (that\n * would make a GET fire four lookups and a write), so there is no\n * per-record status. Poll `verification_status` on the domain instead —\n * it is the result of the automatic 10-minute check.\n *\n * @deprecated Always undefined. Kept so existing code still compiles.\n */\n status?: string;\n}\n\nexport interface CreatedDomain {\n /** Same identifier the list and detail endpoints use */\n id: string;\n domain_name: string;\n verification_status: string;\n created_at: string;\n}\n\nexport interface AdminDomainDetail {\n id: string;\n domain_name: string;\n verification_status: string;\n dmarc_policy: string | null;\n created_at: string;\n dns_records: {\n dkim: DnsRecord;\n spf: DnsRecord[];\n };\n /** Whether the automatic 10-minute DNS check still covers this domain */\n auto_verification: { active: boolean; reason: string };\n}\n\nexport interface EmailLog {\n /** Same value POST /v2/emails returned as message_id */\n id: string;\n status: string;\n subject: string | null;\n from: string | null;\n from_name: string | null;\n to: Array<{ email: string; name?: string }>;\n reply_to: string | null;\n recipients_count: number;\n error: string | null;\n has_attachments: boolean;\n sent_at: string | null;\n created_at: string;\n}\n\nexport interface Paginated<T> {\n data: T[];\n total: number;\n page: number;\n per_page: number;\n}\n\nexport interface ListLogsParams {\n page?: number;\n /** 1-100, default 25 */\n per_page?: number;\n status?: string;\n /** Domain name or domain id */\n domain?: string;\n from?: string;\n to?: string;\n search?: string;\n}\n\nexport interface Suppression {\n email: string;\n bounce_sub_type: string | null;\n bounce_reason: string | null;\n hard_bounced_at: string | null;\n last_complaint_at: string | null;\n complaint_count: number;\n created_at: string;\n updated_at: string;\n}\n\n/** The only events toSend emits. Delivery, open and click do not exist. */\nexport type WebhookEvent = 'bounced' | 'complaint';\n\nexport interface Webhook {\n id: number;\n title: string | null;\n url: string;\n events: WebhookEvent[];\n status: string;\n include_message: boolean;\n created_at: string;\n updated_at: string;\n}\n\nexport interface SendingKey {\n id: number;\n title: string | null;\n /** Masked. The full key is returned only by createSendingKey(). */\n key: string;\n status: string;\n domain: string | null;\n created_at: string;\n}\n\nexport interface CreatedSendingKey {\n id: number;\n name: string;\n /** Shown once. Store it now — it cannot be retrieved later. */\n api_key: string;\n created_at: string;\n}\n\n/** GET /v2/info with an admin key */\nexport interface AdminInfo {\n account: {\n id: number;\n status: string;\n plan_type: string;\n /** The pooled balance for the whole account, never a child team's own */\n credit_balance: number | null;\n credits_shared: boolean;\n limit_per_second: number;\n ses_region: string;\n /** Last 30 days, fraction between 0 and 1 (0.004 = 0.4%) */\n bounce_rate: number;\n /** Last 30 days, fraction between 0 and 1 */\n complaint_rate: number;\n };\n key: {\n id: number;\n title: string;\n type: 'admin';\n /** The team this request acted on */\n acting_on: number;\n /** The team that owns the key */\n owned_by: number;\n scopes: string[];\n };\n /** Present only when the key has `domains:read` */\n domains?: AdminDomain[];\n}\n\nexport interface SuppressionReactivation {\n deleted: boolean;\n message: string;\n used_today: number;\n remaining_today: number;\n daily_limit: number;\n}\n\n/** Where a team sends from. Fixed for the life of the team. */\nexport type SesRegion = 'us-east-1' | 'eu-central-1';\n\nexport interface TeamList {\n account: { id: number; name: string | null; status: string; credit_balance: number | null };\n teams: Array<{ id: number; name: string | null; status: string; ses_region: SesRegion }>;\n /** Credits pool at the parent; child teams have no separate balance */\n credits_shared: boolean;\n}\n\nexport interface AdminConfig {\n /** Your admin API key (tsend_admin_...) */\n apiKey: string;\n /** Act on a child team by default. Overridable per call. */\n accountId?: number;\n baseUrl?: string;\n timeout?: number;\n}\n\n// ============================================================================\n// Admin Client\n// ============================================================================\n\n/**\n * Account-level API client.\n *\n * Deliberately a separate class from `ToSend`: admin keys and sending keys are\n * different credentials and are not interchangeable. A sending key cannot\n * manage the account, and an admin key can only send when it holds the\n * `emails:send` scope. Keeping them on separate clients means the mistake is\n * caught at the type level rather than by a 401 in production.\n *\n * @example\n * ```ts\n * const admin = new ToSendAdmin(process.env.TOSEND_ADMIN_KEY!);\n *\n * const domain = await admin.createDomain('client.example.com');\n * const detail = await admin.getDomain(domain.id);\n * console.log(detail.dns_records.dkim);\n * // No verify call needed — toSend checks DNS every 10 minutes.\n * ```\n */\nexport class ToSendAdmin {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n private readonly accountId?: number;\n\n constructor(config: string | AdminConfig) {\n if (typeof config === 'string') {\n this.apiKey = config;\n this.baseUrl = 'https://api.tosend.com';\n this.timeout = 30000;\n } else {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl?.replace(/\\/$/, '') ?? 'https://api.tosend.com';\n this.timeout = config.timeout ?? 30000;\n this.accountId = config.accountId;\n }\n\n if (!this.apiKey?.trim()) {\n throw new ToSendError('Admin API key is required', 401);\n }\n\n // Caught here rather than as a 401 from the server, because pasting a\n // sending key is the likeliest mistake and the message should say so.\n if (!this.apiKey.startsWith('tsend_admin_')) {\n throw new ToSendError(\n 'This is not an admin API key. Admin keys start with \"tsend_admin_\" and are created in the dashboard under API Keys.',\n 401\n );\n }\n }\n\n /**\n * The team this key acts on, the key itself and its scopes. Needs no scope,\n * so it is the way to check a key works.\n */\n async getInfo(): Promise<AdminInfo> {\n return this.request<AdminInfo>('GET', '/v2/info');\n }\n\n /** Return a client scoped to one of your child teams. */\n forAccount(accountId: number): ToSendAdmin {\n return new ToSendAdmin({\n apiKey: this.apiKey,\n baseUrl: this.baseUrl,\n timeout: this.timeout,\n accountId,\n });\n }\n\n // ── Sending (requires the emails:send scope) ──\n\n /**\n * Send an email as the targeted team (the key's own, or one set with\n * forAccount()). The key must hold the `emails:send` scope.\n */\n async send(params: SendEmailParams): Promise<SendEmailResponse> {\n return this.request<SendEmailResponse>('POST', '/v2/emails', params);\n }\n\n /** Send up to 100 emails in one request; same scope and targeting as send(). */\n async batch(emails: SendEmailParams[]): Promise<BatchEmailResponse> {\n return this.request<BatchEmailResponse>('POST', '/v2/emails/batch', { emails });\n }\n\n // ── Domains ──\n\n async listDomains(params: { page?: number; per_page?: number } = {}): Promise<Paginated<AdminDomain>> {\n return this.request<Paginated<AdminDomain>>('GET', '/v2/domains', undefined, params);\n }\n\n /**\n * Add a sending domain.\n *\n * The domain starts in `draft`. There is NO verify call to make: toSend\n * checks public DNS every 10 minutes and provisions the domain as soon as\n * the records resolve.\n *\n * A duplicate throws a 409. Treat that as \"already exists, go read it\" —\n * domain names are globally unique, so a retry after a timeout hits this\n * for a domain your first call created successfully.\n */\n async createDomain(name: string): Promise<CreatedDomain> {\n const res = await this.request<{ data: CreatedDomain }>('POST', '/v2/domains', { name });\n return res.data;\n }\n\n /** Domain detail plus the DNS records your customer must publish. */\n async getDomain(id: string): Promise<AdminDomainDetail> {\n const res = await this.request<{ data: AdminDomainDetail }>('GET', `/v2/domains/${encodeURIComponent(id)}`);\n return res.data;\n }\n\n /**\n * Remove a domain.\n *\n * Does NOT remove anything from your customer's DNS zone. Records left\n * published can allow the domain to be re-verified elsewhere, so clean them\n * up in your own workflow.\n */\n async deleteDomain(id: string): Promise<void> {\n await this.request('DELETE', `/v2/domains/${encodeURIComponent(id)}`);\n }\n\n // ── Logs ──\n\n async listEmails(params: ListLogsParams = {}): Promise<Paginated<EmailLog>> {\n return this.request<Paginated<EmailLog>>('GET', '/v2/emails', undefined, params);\n }\n\n /** Look up one message by the id POST /v2/emails returned. */\n async getEmail(messageId: string): Promise<EmailLog> {\n const res = await this.request<{ data: EmailLog }>('GET', `/v2/emails/${encodeURIComponent(messageId)}`);\n return res.data;\n }\n\n // ── Suppressions ──\n\n async listSuppressions(params: { page?: number; per_page?: number; search?: string; status?: string } = {}): Promise<Paginated<Suppression>> {\n return this.request<Paginated<Suppression>>('GET', '/v2/suppressions', undefined, params);\n }\n\n /** Limited to 500 per day per account, shared with the dashboard. */\n async deleteSuppression(email: string): Promise<SuppressionReactivation> {\n return this.request<SuppressionReactivation>('DELETE', '/v2/suppressions', undefined, { email });\n }\n\n // ── Webhooks ──\n\n async listWebhooks(params: { page?: number; per_page?: number } = {}): Promise<Paginated<Webhook>> {\n return this.request<Paginated<Webhook>>('GET', '/v2/webhooks', undefined, params);\n }\n\n /**\n * The signing `secret` is generated by toSend. The API returns it only in\n * this response (the dashboard can show it later), so store it now.\n */\n async createWebhook(params: {\n url: string;\n events: WebhookEvent[];\n title?: string;\n include_message?: boolean;\n }): Promise<Webhook & { secret: string }> {\n const res = await this.request<{ data: Webhook & { secret: string } }>('POST', '/v2/webhooks', params);\n return res.data;\n }\n\n async updateWebhook(id: number, params: Partial<{\n url: string;\n events: WebhookEvent[];\n title: string;\n status: 'active' | 'inactive';\n include_message: boolean;\n }>): Promise<Webhook> {\n const res = await this.request<{ data: Webhook }>('PUT', `/v2/webhooks/${id}`, params);\n return res.data;\n }\n\n async deleteWebhook(id: number): Promise<void> {\n await this.request('DELETE', `/v2/webhooks/${id}`);\n }\n\n // ── Sending keys ──\n\n async listSendingKeys(params: { page?: number; per_page?: number } = {}): Promise<Paginated<SendingKey>> {\n return this.request<Paginated<SendingKey>>('GET', '/v2/api-keys', undefined, params);\n }\n\n /**\n * Mint a sending key for this team — how you give each client their own key.\n *\n * The raw key is returned once and never again. Pass `domain` (a domain id)\n * to restrict it to one domain; omit it for all domains on the team.\n * Needs the `api_keys:write` and `emails:send` scopes.\n */\n async createSendingKey(name: string, domain?: string): Promise<CreatedSendingKey> {\n const res = await this.request<{ data: CreatedSendingKey }>('POST', '/v2/api-keys', { name, domain });\n return res.data;\n }\n\n async deleteSendingKey(id: number): Promise<void> {\n await this.request('DELETE', `/v2/api-keys/${id}`);\n }\n\n // ── Teams ──\n\n async listTeams(): Promise<TeamList> {\n const res = await this.request<{ data: TeamList }>('GET', '/v2/teams');\n return res.data;\n }\n\n /**\n * Only the parent account can create teams. `ses_region` defaults to\n * 'us-east-1' and cannot be changed once the team exists.\n */\n async createTeam(\n name: string,\n options: { ses_region?: SesRegion } = {},\n ): Promise<{ id: number; name: string; status: string; ses_region: SesRegion }> {\n const body: Record<string, unknown> = { name };\n if (options.ses_region) body.ses_region = options.ses_region;\n const res = await this.request<{ data: any }>('POST', '/v2/teams', body);\n return res.data;\n }\n\n async updateTeam(id: number, params: { name?: string; status?: 'active' | 'suspended' }): Promise<{ id: number; name: string; status: string }> {\n const res = await this.request<{ data: any }>('PUT', `/v2/teams/${id}`, params);\n return res.data;\n }\n\n // ── Private ──\n\n private async request<T>(\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n endpoint: string,\n body?: unknown,\n query?: Record<string, string | number | undefined> | ListLogsParams | { page?: number; per_page?: number }\n ): Promise<T> {\n let url = `${this.baseUrl}${endpoint}`;\n\n if (query) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query as Record<string, unknown>)) {\n if (value !== undefined && value !== '') params.set(key, String(value));\n }\n const qs = params.toString();\n if (qs) url += `?${qs}`;\n }\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n\n if (this.accountId !== undefined) {\n headers['X-Account-Id'] = String(this.accountId);\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const data = await response.json() as T & {\n message?: string;\n errors?: ValidationErrors;\n error_type?: ErrorType;\n };\n\n if (!response.ok) {\n throw new ToSendError(\n data.message ?? 'Unknown error',\n response.status,\n data.errors ?? {},\n data.error_type\n );\n }\n\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof ToSendError) throw error;\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') throw new ToSendError('Request timeout', 408);\n throw new ToSendError(error.message);\n }\n\n throw new ToSendError('Unknown error');\n }\n }\n}\n\n// ============================================================================\n// Default Export\n// ============================================================================\n\nexport default ToSend;\n"],"mappings":";AA8IO,IAAM,cAAN,MAAM,qBAAoB,MAAM;AAAA,EAC5B;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EAET,OAAwB,iBAA4C;AAAA,IAClE,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EAEA,YACE,SACA,OAAe,GACf,SAA2B,CAAC,GAC5B,WACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,YAAY,aAAa,aAAY,eAAe,IAAI,KAAK;AAAA,EACpE;AAAA,EAEA,IAAI,oBAA6B;AAC/B,WAAO,KAAK,cAAc,sBAAsB,KAAK,SAAS;AAAA,EAChE;AAAA,EAEA,IAAI,wBAAiC;AACnC,WACE,KAAK,cAAc,kBACnB,KAAK,cAAc,eACnB,KAAK,SAAS,OACd,KAAK,SAAS;AAAA,EAElB;AAAA,EAEA,IAAI,mBAA4B;AAC9B,WAAO,KAAK,cAAc,yBAAyB,KAAK,SAAS;AAAA,EACnE;AACF;AAMO,IAAM,SAAN,MAAa;AAAA,EACD;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAA+B;AACzC,QAAI,OAAO,WAAW,UAAU;AAC9B,WAAK,SAAS;AACd,WAAK,UAAU;AACf,WAAK,UAAU;AAAA,IACjB,OAAO;AACL,WAAK,SAAS,OAAO;AACrB,WAAK,UAAU,OAAO,SAAS,QAAQ,OAAO,EAAE,KAAK;AACrD,WAAK,UAAU,OAAO,WAAW;AAAA,IACnC;AAEA,QAAI,CAAC,KAAK,QAAQ,KAAK,GAAG;AACxB,YAAM,IAAI,YAAY,uBAAuB,GAAG;AAAA,IAClD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,KAAK,QAAqD;AAC9D,SAAK,oBAAoB,MAAM;AAC/B,WAAO,KAAK,QAA2B,QAAQ,cAAc,MAAM;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6BA,MAAM,MAAM,QAAwD;AAClE,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,IAAI,YAAY,gDAAgD,KAAK;AAAA,QACzE,QAAQ,EAAE,UAAU,iCAAiC;AAAA,MACvD,CAAC;AAAA,IACH;AAEA,WAAO,QAAQ,CAAC,OAAO,UAAU;AAC/B,UAAI;AACF,aAAK,oBAAoB,KAAK;AAAA,MAChC,SAAS,OAAO;AACd,YAAI,iBAAiB,aAAa;AAChC,gBAAM,IAAI;AAAA,YACR,kBAAkB,KAAK,KAAK,MAAM,OAAO;AAAA,YACzC,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAAA,QACF;AACA,cAAM;AAAA,MACR;AAAA,IACF,CAAC;AAED,WAAO,KAAK,QAA4B,QAAQ,oBAAoB,EAAE,OAAO,CAAC;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,iBAAuC;AAC3C,WAAO,KAAK,QAAqB,OAAO,UAAU;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAoB,QAA+B;AACzD,UAAM,SAAiD,CAAC;AAExD,QAAI,CAAC,OAAO,MAAM,OAAO;AACvB,aAAO,OAAO,EAAE,UAAU,yBAAyB;AAAA,IACrD,WAAW,CAAC,KAAK,aAAa,OAAO,KAAK,KAAK,GAAG;AAChD,aAAO,OAAO,EAAE,SAAS,wBAAwB;AAAA,IACnD;AAEA,QAAI,CAAC,OAAO,IAAI,QAAQ;AACtB,aAAO,KAAK,EAAE,UAAU,iBAAiB;AAAA,IAC3C,OAAO;AACL,YAAM,oBAAoB,OAAO,GAAG;AAAA,QAClC,CAAC,MAAM,EAAE,SAAS,KAAK,aAAa,EAAE,KAAK;AAAA,MAC7C;AACA,UAAI,CAAC,mBAAmB;AACtB,eAAO,KAAK,EAAE,SAAS,iDAAiD;AAAA,MAC1E;AAAA,IACF;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,UAAU,EAAE,UAAU,sBAAsB;AAAA,IACrD;AAEA,QAAI,CAAC,OAAO,QAAQ,CAAC,OAAO,MAAM;AAChC,aAAO,UAAU,EAAE,UAAU,0CAA0C;AAAA,IACzE;AAEA,QAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAClC,YAAM,aAAa,OAAO,OAAO,MAAM,EAAE,CAAC;AAC1C,YAAM,UAAU,OAAO,OAAO,UAAU,EAAE,CAAC;AAC3C,YAAM,IAAI,YAAY,SAAS,KAAK,MAAM;AAAA,IAC5C;AAAA,EACF;AAAA,EAEQ,aAAa,OAAwB;AAC3C,WAAO,6BAA6B,KAAK,KAAK;AAAA,EAChD;AAAA,EAEA,MAAc,QACZ,QACA,UACA,MACY;AACZ,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,QAAQ;AAEtC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAEnE,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC;AAAA,QACA,SAAS;AAAA,UACP,eAAe,UAAU,KAAK,MAAM;AAAA,UACpC,gBAAgB;AAAA,UAChB,QAAQ;AAAA,QACV;AAAA,QACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,QACpC,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,mBAAa,SAAS;AAEtB,YAAM,OAAO,MAAM,SAAS,KAAK;AAMjC,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI;AAAA,UACR,KAAK,WAAW;AAAA,UAChB,SAAS;AAAA,UACT,KAAK,UAAU,CAAC;AAAA,UAChB,KAAK;AAAA,QACP;AAAA,MACF;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,mBAAa,SAAS;AAEtB,UAAI,iBAAiB,aAAa;AAChC,cAAM;AAAA,MACR;AAEA,UAAI,iBAAiB,OAAO;AAC1B,YAAI,MAAM,SAAS,cAAc;AAC/B,gBAAM,IAAI,YAAY,mBAAmB,GAAG;AAAA,QAC9C;AACA,cAAM,IAAI,YAAY,MAAM,OAAO;AAAA,MACrC;AAEA,YAAM,IAAI,YAAY,eAAe;AAAA,IACvC;AAAA,EACF;AACF;AA0NO,IAAM,cAAN,MAAM,aAAY;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAA8B;AACxC,QAAI,OAAO,WAAW,UAAU;AAC9B,WAAK,SAAS;AACd,WAAK,UAAU;AACf,WAAK,UAAU;AAAA,IACjB,OAAO;AACL,WAAK,SAAS,OAAO;AACrB,WAAK,UAAU,OAAO,SAAS,QAAQ,OAAO,EAAE,KAAK;AACrD,WAAK,UAAU,OAAO,WAAW;AACjC,WAAK,YAAY,OAAO;AAAA,IAC1B;AAEA,QAAI,CAAC,KAAK,QAAQ,KAAK,GAAG;AACxB,YAAM,IAAI,YAAY,6BAA6B,GAAG;AAAA,IACxD;AAIA,QAAI,CAAC,KAAK,OAAO,WAAW,cAAc,GAAG;AAC3C,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAA8B;AAClC,WAAO,KAAK,QAAmB,OAAO,UAAU;AAAA,EAClD;AAAA;AAAA,EAGA,WAAW,WAAgC;AACzC,WAAO,IAAI,aAAY;AAAA,MACrB,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,KAAK,QAAqD;AAC9D,WAAO,KAAK,QAA2B,QAAQ,cAAc,MAAM;AAAA,EACrE;AAAA;AAAA,EAGA,MAAM,MAAM,QAAwD;AAClE,WAAO,KAAK,QAA4B,QAAQ,oBAAoB,EAAE,OAAO,CAAC;AAAA,EAChF;AAAA;AAAA,EAIA,MAAM,YAAY,SAA+C,CAAC,GAAoC;AACpG,WAAO,KAAK,QAAgC,OAAO,eAAe,QAAW,MAAM;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,aAAa,MAAsC;AACvD,UAAM,MAAM,MAAM,KAAK,QAAiC,QAAQ,eAAe,EAAE,KAAK,CAAC;AACvF,WAAO,IAAI;AAAA,EACb;AAAA;AAAA,EAGA,MAAM,UAAU,IAAwC;AACtD,UAAM,MAAM,MAAM,KAAK,QAAqC,OAAO,eAAe,mBAAmB,EAAE,CAAC,EAAE;AAC1G,WAAO,IAAI;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,aAAa,IAA2B;AAC5C,UAAM,KAAK,QAAQ,UAAU,eAAe,mBAAmB,EAAE,CAAC,EAAE;AAAA,EACtE;AAAA;AAAA,EAIA,MAAM,WAAW,SAAyB,CAAC,GAAiC;AAC1E,WAAO,KAAK,QAA6B,OAAO,cAAc,QAAW,MAAM;AAAA,EACjF;AAAA;AAAA,EAGA,MAAM,SAAS,WAAsC;AACnD,UAAM,MAAM,MAAM,KAAK,QAA4B,OAAO,cAAc,mBAAmB,SAAS,CAAC,EAAE;AACvG,WAAO,IAAI;AAAA,EACb;AAAA;AAAA,EAIA,MAAM,iBAAiB,SAAiF,CAAC,GAAoC;AAC3I,WAAO,KAAK,QAAgC,OAAO,oBAAoB,QAAW,MAAM;AAAA,EAC1F;AAAA;AAAA,EAGA,MAAM,kBAAkB,OAAiD;AACvE,WAAO,KAAK,QAAiC,UAAU,oBAAoB,QAAW,EAAE,MAAM,CAAC;AAAA,EACjG;AAAA;AAAA,EAIA,MAAM,aAAa,SAA+C,CAAC,GAAgC;AACjG,WAAO,KAAK,QAA4B,OAAO,gBAAgB,QAAW,MAAM;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,QAKsB;AACxC,UAAM,MAAM,MAAM,KAAK,QAAgD,QAAQ,gBAAgB,MAAM;AACrG,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,cAAc,IAAY,QAMV;AACpB,UAAM,MAAM,MAAM,KAAK,QAA2B,OAAO,gBAAgB,EAAE,IAAI,MAAM;AACrF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,cAAc,IAA2B;AAC7C,UAAM,KAAK,QAAQ,UAAU,gBAAgB,EAAE,EAAE;AAAA,EACnD;AAAA;AAAA,EAIA,MAAM,gBAAgB,SAA+C,CAAC,GAAmC;AACvG,WAAO,KAAK,QAA+B,OAAO,gBAAgB,QAAW,MAAM;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,iBAAiB,MAAc,QAA6C;AAChF,UAAM,MAAM,MAAM,KAAK,QAAqC,QAAQ,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACpG,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,iBAAiB,IAA2B;AAChD,UAAM,KAAK,QAAQ,UAAU,gBAAgB,EAAE,EAAE;AAAA,EACnD;AAAA;AAAA,EAIA,MAAM,YAA+B;AACnC,UAAM,MAAM,MAAM,KAAK,QAA4B,OAAO,WAAW;AACrE,WAAO,IAAI;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WACJ,MACA,UAAsC,CAAC,GACuC;AAC9E,UAAM,OAAgC,EAAE,KAAK;AAC7C,QAAI,QAAQ,WAAY,MAAK,aAAa,QAAQ;AAClD,UAAM,MAAM,MAAM,KAAK,QAAuB,QAAQ,aAAa,IAAI;AACvE,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,WAAW,IAAY,QAAmH;AAC9I,UAAM,MAAM,MAAM,KAAK,QAAuB,OAAO,aAAa,EAAE,IAAI,MAAM;AAC9E,WAAO,IAAI;AAAA,EACb;AAAA;AAAA,EAIA,MAAc,QACZ,QACA,UACA,MACA,OACY;AACZ,QAAI,MAAM,GAAG,KAAK,OAAO,GAAG,QAAQ;AAEpC,QAAI,OAAO;AACT,YAAM,SAAS,IAAI,gBAAgB;AACnC,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAgC,GAAG;AAC3E,YAAI,UAAU,UAAa,UAAU,GAAI,QAAO,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MACxE;AACA,YAAM,KAAK,OAAO,SAAS;AAC3B,UAAI,GAAI,QAAO,IAAI,EAAE;AAAA,IACvB;AAEA,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,gBAAgB;AAAA,MAChB,QAAQ;AAAA,IACV;AAEA,QAAI,KAAK,cAAc,QAAW;AAChC,cAAQ,cAAc,IAAI,OAAO,KAAK,SAAS;AAAA,IACjD;AAEA,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAEnE,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK;AAAA,QAChC;AAAA,QACA;AAAA,QACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,QACpC,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,mBAAa,SAAS;AAEtB,YAAM,OAAO,MAAM,SAAS,KAAK;AAMjC,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI;AAAA,UACR,KAAK,WAAW;AAAA,UAChB,SAAS;AAAA,UACT,KAAK,UAAU,CAAC;AAAA,UAChB,KAAK;AAAA,QACP;AAAA,MACF;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,mBAAa,SAAS;AAEtB,UAAI,iBAAiB,YAAa,OAAM;AAExC,UAAI,iBAAiB,OAAO;AAC1B,YAAI,MAAM,SAAS,aAAc,OAAM,IAAI,YAAY,mBAAmB,GAAG;AAC7E,cAAM,IAAI,YAAY,MAAM,OAAO;AAAA,MACrC;AAEA,YAAM,IAAI,YAAY,eAAe;AAAA,IACvC;AAAA,EACF;AACF;AAMA,IAAO,gBAAQ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tosend",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Official Node.js SDK for the ToSend email API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",