tosend 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -3
- package/dist/index.d.mts +360 -12
- package/dist/index.d.ts +360 -12
- package/dist/index.js +226 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +225 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,15 +110,46 @@ response.results.forEach((result) => {
|
|
|
110
110
|
```typescript
|
|
111
111
|
const info = await tosend.getAccountInfo();
|
|
112
112
|
|
|
113
|
-
console.log(info.account.
|
|
114
|
-
console.log(info.account.
|
|
115
|
-
console.log(info.account.
|
|
113
|
+
console.log(info.account.status); // 'active'
|
|
114
|
+
console.log(info.account.credit_balance); // null = no credit limit
|
|
115
|
+
console.log(info.account.limit_per_second);
|
|
116
116
|
|
|
117
117
|
info.domains.forEach((domain) => {
|
|
118
118
|
console.log(domain.domain_name, domain.verification_status);
|
|
119
119
|
});
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
## Admin API
|
|
123
|
+
|
|
124
|
+
`ToSendAdmin` is a separate client for the [Admin API](https://tosend.com/docs/api/admin-api). It takes an admin key (`tsend_admin_...`) and refuses a sending key at construction. Each call needs the matching [scope](https://tosend.com/docs/api/admin-api#scopes) on the key.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { ToSendAdmin } from 'tosend';
|
|
128
|
+
|
|
129
|
+
const admin = new ToSendAdmin(process.env.TOSEND_ADMIN_KEY!);
|
|
130
|
+
|
|
131
|
+
// Act on one of your client teams (sends X-Account-Id)
|
|
132
|
+
const client = admin.forAccount(4203);
|
|
133
|
+
|
|
134
|
+
const domain = await client.createDomain('client.example.com');
|
|
135
|
+
const detail = await client.getDomain(domain.id);
|
|
136
|
+
console.log(detail.dns_records.dkim); // show these records to your client
|
|
137
|
+
|
|
138
|
+
// Needs api_keys:write and emails:send
|
|
139
|
+
const key = await client.createSendingKey('Client: Acme Corp');
|
|
140
|
+
console.log(key.api_key); // returned once, store it now
|
|
141
|
+
|
|
142
|
+
// Needs emails:send
|
|
143
|
+
await client.send({
|
|
144
|
+
from: { email: 'hello@client.example.com' },
|
|
145
|
+
to: [{ email: 'user@example.com' }],
|
|
146
|
+
subject: 'Hi',
|
|
147
|
+
html: '<p>Hello</p>',
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
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 }`.
|
|
152
|
+
|
|
122
153
|
## Configuration
|
|
123
154
|
|
|
124
155
|
### Simple (API key only)
|
package/dist/index.d.mts
CHANGED
|
@@ -45,31 +45,63 @@ interface BatchEmailResult {
|
|
|
45
45
|
status: 'success' | 'error' | 'spam';
|
|
46
46
|
message_id?: string;
|
|
47
47
|
message?: string;
|
|
48
|
-
errors?:
|
|
48
|
+
errors?: ValidationErrors;
|
|
49
49
|
}
|
|
50
50
|
interface BatchEmailResponse {
|
|
51
51
|
results: BatchEmailResult[];
|
|
52
52
|
}
|
|
53
53
|
interface Domain {
|
|
54
|
+
id: number;
|
|
54
55
|
domain_name: string;
|
|
55
|
-
verification_status:
|
|
56
|
-
|
|
56
|
+
verification_status: string;
|
|
57
|
+
/** 1 for the account's default domain, else 0 */
|
|
58
|
+
is_default: number;
|
|
59
|
+
total_sent: number;
|
|
60
|
+
total_bounced: number;
|
|
61
|
+
total_complained: number;
|
|
57
62
|
}
|
|
58
63
|
interface AccountInfo {
|
|
59
64
|
account: {
|
|
60
|
-
|
|
61
|
-
plan_type: string;
|
|
65
|
+
id: number;
|
|
62
66
|
status: string;
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
plan_type: string;
|
|
68
|
+
/** null means the account does not consume credits */
|
|
69
|
+
credit_balance: number | null;
|
|
70
|
+
monthly_email_limit: number;
|
|
71
|
+
limit_per_second: number;
|
|
72
|
+
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;
|
|
77
|
+
ses_status: string;
|
|
65
78
|
};
|
|
66
79
|
domains: Domain[];
|
|
67
80
|
}
|
|
68
81
|
interface ToSendErrorResponse {
|
|
69
|
-
|
|
82
|
+
status_code: number;
|
|
83
|
+
error_type: ErrorType;
|
|
70
84
|
message: string;
|
|
71
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Field-level validation detail.
|
|
87
|
+
*
|
|
88
|
+
* The sending API returns a nested map (`{subject: {required: "..."}}`);
|
|
89
|
+
* the Worker's ApiError returns a flat one (`{name: "required"}`). Both
|
|
90
|
+
* shapes reach this type, so consumers must not assume either — see
|
|
91
|
+
* ToSendError.errors.
|
|
92
|
+
*/
|
|
93
|
+
errors?: ValidationErrors;
|
|
72
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Either shape the API may return for `errors`.
|
|
97
|
+
*
|
|
98
|
+
* ToSendError declared the nested form only, so `err.errors.name` was typed
|
|
99
|
+
* as an object while the Worker actually sends the string `'required'`. That
|
|
100
|
+
* compiled because the response was cast, and would have surfaced as an
|
|
101
|
+
* undefined property at runtime.
|
|
102
|
+
*/
|
|
103
|
+
type ValidationErrors = Record<string, string | Record<string, string>>;
|
|
104
|
+
type ErrorType = 'bad_request' | 'unauthorized' | 'forbidden' | 'not_found' | 'validation_error' | 'rate_limit_exceeded' | 'internal_error';
|
|
73
105
|
interface ToSendConfig {
|
|
74
106
|
/** Your ToSend API key */
|
|
75
107
|
apiKey: string;
|
|
@@ -80,8 +112,11 @@ interface ToSendConfig {
|
|
|
80
112
|
}
|
|
81
113
|
declare class ToSendError extends Error {
|
|
82
114
|
readonly code: number;
|
|
83
|
-
readonly
|
|
84
|
-
|
|
115
|
+
readonly errorType: ErrorType;
|
|
116
|
+
/** See ValidationErrors — the value may be a string or a nested map. */
|
|
117
|
+
readonly errors: ValidationErrors;
|
|
118
|
+
private static readonly ERROR_TYPE_MAP;
|
|
119
|
+
constructor(message: string, code?: number, errors?: ValidationErrors, errorType?: ErrorType);
|
|
85
120
|
get isValidationError(): boolean;
|
|
86
121
|
get isAuthenticationError(): boolean;
|
|
87
122
|
get isRateLimitError(): boolean;
|
|
@@ -150,5 +185,318 @@ declare class ToSend {
|
|
|
150
185
|
private isValidEmail;
|
|
151
186
|
private request;
|
|
152
187
|
}
|
|
188
|
+
interface AdminDomain {
|
|
189
|
+
/** Domain identifier used across the Admin API */
|
|
190
|
+
id: string;
|
|
191
|
+
domain_name: string;
|
|
192
|
+
verification_status: 'draft' | 'pending' | 'verified' | 'failed';
|
|
193
|
+
is_default: boolean;
|
|
194
|
+
ses_region: string | null;
|
|
195
|
+
total_sent: number;
|
|
196
|
+
total_bounced: number;
|
|
197
|
+
total_complained: number;
|
|
198
|
+
updated_at: string;
|
|
199
|
+
}
|
|
200
|
+
interface DnsRecord {
|
|
201
|
+
type: 'TXT' | 'MX';
|
|
202
|
+
name: string;
|
|
203
|
+
content: string;
|
|
204
|
+
TTL: string;
|
|
205
|
+
priority: string;
|
|
206
|
+
/**
|
|
207
|
+
* NOT returned by the Admin API.
|
|
208
|
+
*
|
|
209
|
+
* The domain-detail endpoint does not resolve DNS on your behalf (that
|
|
210
|
+
* would make a GET fire four lookups and a write), so there is no
|
|
211
|
+
* per-record status. Poll `verification_status` on the domain instead —
|
|
212
|
+
* it is the result of the automatic 10-minute check.
|
|
213
|
+
*
|
|
214
|
+
* @deprecated Always undefined. Kept so existing code still compiles.
|
|
215
|
+
*/
|
|
216
|
+
status?: string;
|
|
217
|
+
}
|
|
218
|
+
interface CreatedDomain {
|
|
219
|
+
/** Same identifier the list and detail endpoints use */
|
|
220
|
+
id: string;
|
|
221
|
+
domain_name: string;
|
|
222
|
+
verification_status: string;
|
|
223
|
+
created_at: string;
|
|
224
|
+
}
|
|
225
|
+
interface AdminDomainDetail {
|
|
226
|
+
id: string;
|
|
227
|
+
domain_name: string;
|
|
228
|
+
verification_status: string;
|
|
229
|
+
dmarc_policy: string | null;
|
|
230
|
+
created_at: string;
|
|
231
|
+
dns_records: {
|
|
232
|
+
dkim: DnsRecord;
|
|
233
|
+
spf: DnsRecord[];
|
|
234
|
+
};
|
|
235
|
+
/** Whether the automatic 10-minute DNS check still covers this domain */
|
|
236
|
+
auto_verification: {
|
|
237
|
+
active: boolean;
|
|
238
|
+
reason: string;
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
interface EmailLog {
|
|
242
|
+
/** Same value POST /v2/emails returned as message_id */
|
|
243
|
+
id: string;
|
|
244
|
+
status: string;
|
|
245
|
+
subject: string | null;
|
|
246
|
+
from: string | null;
|
|
247
|
+
from_name: string | null;
|
|
248
|
+
to: Array<{
|
|
249
|
+
email: string;
|
|
250
|
+
name?: string;
|
|
251
|
+
}>;
|
|
252
|
+
reply_to: string | null;
|
|
253
|
+
recipients_count: number;
|
|
254
|
+
error: string | null;
|
|
255
|
+
has_attachments: boolean;
|
|
256
|
+
sent_at: string | null;
|
|
257
|
+
created_at: string;
|
|
258
|
+
}
|
|
259
|
+
interface Paginated<T> {
|
|
260
|
+
data: T[];
|
|
261
|
+
total: number;
|
|
262
|
+
page: number;
|
|
263
|
+
per_page: number;
|
|
264
|
+
}
|
|
265
|
+
interface ListLogsParams {
|
|
266
|
+
page?: number;
|
|
267
|
+
/** 1-100, default 25 */
|
|
268
|
+
per_page?: number;
|
|
269
|
+
status?: string;
|
|
270
|
+
/** Domain name or domain id */
|
|
271
|
+
domain?: string;
|
|
272
|
+
from?: string;
|
|
273
|
+
to?: string;
|
|
274
|
+
search?: string;
|
|
275
|
+
}
|
|
276
|
+
interface Suppression {
|
|
277
|
+
email: string;
|
|
278
|
+
bounce_sub_type: string | null;
|
|
279
|
+
bounce_reason: string | null;
|
|
280
|
+
hard_bounced_at: string | null;
|
|
281
|
+
last_complaint_at: string | null;
|
|
282
|
+
complaint_count: number;
|
|
283
|
+
created_at: string;
|
|
284
|
+
updated_at: string;
|
|
285
|
+
}
|
|
286
|
+
/** The only events toSend emits. Delivery, open and click do not exist. */
|
|
287
|
+
type WebhookEvent = 'bounced' | 'complaint';
|
|
288
|
+
interface Webhook {
|
|
289
|
+
id: number;
|
|
290
|
+
title: string | null;
|
|
291
|
+
url: string;
|
|
292
|
+
events: WebhookEvent[];
|
|
293
|
+
status: string;
|
|
294
|
+
include_message: boolean;
|
|
295
|
+
created_at: string;
|
|
296
|
+
updated_at: string;
|
|
297
|
+
}
|
|
298
|
+
interface SendingKey {
|
|
299
|
+
id: number;
|
|
300
|
+
title: string | null;
|
|
301
|
+
/** Masked. The full key is returned only by createSendingKey(). */
|
|
302
|
+
key: string;
|
|
303
|
+
status: string;
|
|
304
|
+
domain: string | null;
|
|
305
|
+
created_at: string;
|
|
306
|
+
}
|
|
307
|
+
interface CreatedSendingKey {
|
|
308
|
+
id: number;
|
|
309
|
+
name: string;
|
|
310
|
+
/** Shown once. Store it now — it cannot be retrieved later. */
|
|
311
|
+
api_key: string;
|
|
312
|
+
created_at: string;
|
|
313
|
+
}
|
|
314
|
+
/** GET /v2/info with an admin key */
|
|
315
|
+
interface AdminInfo {
|
|
316
|
+
account: {
|
|
317
|
+
id: number;
|
|
318
|
+
status: string;
|
|
319
|
+
plan_type: string;
|
|
320
|
+
/** The pooled balance for the whole account, never a child team's own */
|
|
321
|
+
credit_balance: number | null;
|
|
322
|
+
credits_shared: boolean;
|
|
323
|
+
limit_per_second: number;
|
|
324
|
+
ses_region: string;
|
|
325
|
+
bounce_rate: number;
|
|
326
|
+
complaint_rate: number;
|
|
327
|
+
};
|
|
328
|
+
key: {
|
|
329
|
+
id: number;
|
|
330
|
+
title: string;
|
|
331
|
+
type: 'admin';
|
|
332
|
+
/** The team this request acted on */
|
|
333
|
+
acting_on: number;
|
|
334
|
+
/** The team that owns the key */
|
|
335
|
+
owned_by: number;
|
|
336
|
+
scopes: string[];
|
|
337
|
+
};
|
|
338
|
+
/** Present only when the key has `domains:read` */
|
|
339
|
+
domains?: AdminDomain[];
|
|
340
|
+
}
|
|
341
|
+
interface SuppressionReactivation {
|
|
342
|
+
deleted: boolean;
|
|
343
|
+
message: string;
|
|
344
|
+
used_today: number;
|
|
345
|
+
remaining_today: number;
|
|
346
|
+
daily_limit: number;
|
|
347
|
+
}
|
|
348
|
+
interface TeamList {
|
|
349
|
+
account: {
|
|
350
|
+
id: number;
|
|
351
|
+
name: string | null;
|
|
352
|
+
status: string;
|
|
353
|
+
credit_balance: number | null;
|
|
354
|
+
};
|
|
355
|
+
teams: Array<{
|
|
356
|
+
id: number;
|
|
357
|
+
name: string | null;
|
|
358
|
+
status: string;
|
|
359
|
+
}>;
|
|
360
|
+
/** Credits pool at the parent; child teams have no separate balance */
|
|
361
|
+
credits_shared: boolean;
|
|
362
|
+
}
|
|
363
|
+
interface AdminConfig {
|
|
364
|
+
/** Your admin API key (tsend_admin_...) */
|
|
365
|
+
apiKey: string;
|
|
366
|
+
/** Act on a child team by default. Overridable per call. */
|
|
367
|
+
accountId?: number;
|
|
368
|
+
baseUrl?: string;
|
|
369
|
+
timeout?: number;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Account-level API client.
|
|
373
|
+
*
|
|
374
|
+
* Deliberately a separate class from `ToSend`: admin keys and sending keys are
|
|
375
|
+
* different credentials and are not interchangeable. A sending key cannot
|
|
376
|
+
* manage the account, and an admin key can only send when it holds the
|
|
377
|
+
* `emails:send` scope. Keeping them on separate clients means the mistake is
|
|
378
|
+
* caught at the type level rather than by a 401 in production.
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```ts
|
|
382
|
+
* const admin = new ToSendAdmin(process.env.TOSEND_ADMIN_KEY!);
|
|
383
|
+
*
|
|
384
|
+
* const domain = await admin.createDomain('client.example.com');
|
|
385
|
+
* const detail = await admin.getDomain(domain.id);
|
|
386
|
+
* console.log(detail.dns_records.dkim);
|
|
387
|
+
* // No verify call needed — toSend checks DNS every 10 minutes.
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
declare class ToSendAdmin {
|
|
391
|
+
private readonly apiKey;
|
|
392
|
+
private readonly baseUrl;
|
|
393
|
+
private readonly timeout;
|
|
394
|
+
private readonly accountId?;
|
|
395
|
+
constructor(config: string | AdminConfig);
|
|
396
|
+
/**
|
|
397
|
+
* The team this key acts on, the key itself and its scopes. Needs no scope,
|
|
398
|
+
* so it is the way to check a key works.
|
|
399
|
+
*/
|
|
400
|
+
getInfo(): Promise<AdminInfo>;
|
|
401
|
+
/** Return a client scoped to one of your child teams. */
|
|
402
|
+
forAccount(accountId: number): ToSendAdmin;
|
|
403
|
+
/**
|
|
404
|
+
* Send an email as the targeted team (the key's own, or one set with
|
|
405
|
+
* forAccount()). The key must hold the `emails:send` scope.
|
|
406
|
+
*/
|
|
407
|
+
send(params: SendEmailParams): Promise<SendEmailResponse>;
|
|
408
|
+
/** Send up to 100 emails in one request; same scope and targeting as send(). */
|
|
409
|
+
batch(emails: SendEmailParams[]): Promise<BatchEmailResponse>;
|
|
410
|
+
listDomains(params?: {
|
|
411
|
+
page?: number;
|
|
412
|
+
per_page?: number;
|
|
413
|
+
}): Promise<Paginated<AdminDomain>>;
|
|
414
|
+
/**
|
|
415
|
+
* Add a sending domain.
|
|
416
|
+
*
|
|
417
|
+
* The domain starts in `draft`. There is NO verify call to make: toSend
|
|
418
|
+
* checks public DNS every 10 minutes and provisions the domain as soon as
|
|
419
|
+
* the records resolve.
|
|
420
|
+
*
|
|
421
|
+
* A duplicate throws a 409. Treat that as "already exists, go read it" —
|
|
422
|
+
* domain names are globally unique, so a retry after a timeout hits this
|
|
423
|
+
* for a domain your first call created successfully.
|
|
424
|
+
*/
|
|
425
|
+
createDomain(name: string): Promise<CreatedDomain>;
|
|
426
|
+
/** Domain detail plus the DNS records your customer must publish. */
|
|
427
|
+
getDomain(id: string): Promise<AdminDomainDetail>;
|
|
428
|
+
/**
|
|
429
|
+
* Remove a domain.
|
|
430
|
+
*
|
|
431
|
+
* Does NOT remove anything from your customer's DNS zone. Records left
|
|
432
|
+
* published can allow the domain to be re-verified elsewhere, so clean them
|
|
433
|
+
* up in your own workflow.
|
|
434
|
+
*/
|
|
435
|
+
deleteDomain(id: string): Promise<void>;
|
|
436
|
+
listEmails(params?: ListLogsParams): Promise<Paginated<EmailLog>>;
|
|
437
|
+
/** Look up one message by the id POST /v2/emails returned. */
|
|
438
|
+
getEmail(messageId: string): Promise<EmailLog>;
|
|
439
|
+
listSuppressions(params?: {
|
|
440
|
+
page?: number;
|
|
441
|
+
per_page?: number;
|
|
442
|
+
search?: string;
|
|
443
|
+
status?: string;
|
|
444
|
+
}): Promise<Paginated<Suppression>>;
|
|
445
|
+
/** Limited to 500 per day per account, shared with the dashboard. */
|
|
446
|
+
deleteSuppression(email: string): Promise<SuppressionReactivation>;
|
|
447
|
+
listWebhooks(params?: {
|
|
448
|
+
page?: number;
|
|
449
|
+
per_page?: number;
|
|
450
|
+
}): Promise<Paginated<Webhook>>;
|
|
451
|
+
/**
|
|
452
|
+
* The signing `secret` is generated by toSend. The API returns it only in
|
|
453
|
+
* this response (the dashboard can show it later), so store it now.
|
|
454
|
+
*/
|
|
455
|
+
createWebhook(params: {
|
|
456
|
+
url: string;
|
|
457
|
+
events: WebhookEvent[];
|
|
458
|
+
title?: string;
|
|
459
|
+
include_message?: boolean;
|
|
460
|
+
}): Promise<Webhook & {
|
|
461
|
+
secret: string;
|
|
462
|
+
}>;
|
|
463
|
+
updateWebhook(id: number, params: Partial<{
|
|
464
|
+
url: string;
|
|
465
|
+
events: WebhookEvent[];
|
|
466
|
+
title: string;
|
|
467
|
+
status: 'active' | 'inactive';
|
|
468
|
+
include_message: boolean;
|
|
469
|
+
}>): Promise<Webhook>;
|
|
470
|
+
deleteWebhook(id: number): Promise<void>;
|
|
471
|
+
listSendingKeys(params?: {
|
|
472
|
+
page?: number;
|
|
473
|
+
per_page?: number;
|
|
474
|
+
}): Promise<Paginated<SendingKey>>;
|
|
475
|
+
/**
|
|
476
|
+
* Mint a sending key for this team — how you give each client their own key.
|
|
477
|
+
*
|
|
478
|
+
* The raw key is returned once and never again. Pass `domain` (a domain id)
|
|
479
|
+
* to restrict it to one domain; omit it for all domains on the team.
|
|
480
|
+
* Needs the `api_keys:write` and `emails:send` scopes.
|
|
481
|
+
*/
|
|
482
|
+
createSendingKey(name: string, domain?: string): Promise<CreatedSendingKey>;
|
|
483
|
+
deleteSendingKey(id: number): Promise<void>;
|
|
484
|
+
listTeams(): Promise<TeamList>;
|
|
485
|
+
/** Only the parent account can create teams. */
|
|
486
|
+
createTeam(name: string): Promise<{
|
|
487
|
+
id: number;
|
|
488
|
+
name: string;
|
|
489
|
+
status: string;
|
|
490
|
+
}>;
|
|
491
|
+
updateTeam(id: number, params: {
|
|
492
|
+
name?: string;
|
|
493
|
+
status?: 'active' | 'suspended';
|
|
494
|
+
}): Promise<{
|
|
495
|
+
id: number;
|
|
496
|
+
name: string;
|
|
497
|
+
status: string;
|
|
498
|
+
}>;
|
|
499
|
+
private request;
|
|
500
|
+
}
|
|
153
501
|
|
|
154
|
-
export { type AccountInfo, type Address, type Attachment, type BatchEmailResponse, type BatchEmailResult, type Domain, type SendEmailParams, type SendEmailResponse, ToSend, type ToSendConfig, ToSendError, type ToSendErrorResponse, ToSend as default };
|
|
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 };
|