@neschadin/sendgrid-mcp 0.0.0-stage → 3.0.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/LICENSE +21 -0
- package/MCP_TOOLS.md +457 -0
- package/README.md +175 -2
- package/bin/sendgrid-mcp +2 -0
- package/mcp.json.example +9 -0
- package/package.json +63 -4
- package/src/client.ts +1585 -0
- package/src/config.ts +197 -0
- package/src/http.ts +132 -0
- package/src/index.ts +147 -0
- package/src/logger.ts +43 -0
- package/src/redact.ts +91 -0
- package/src/tool_signal.ts +50 -0
- package/src/tools/account.ts +566 -0
- package/src/tools/classify_error.ts +133 -0
- package/src/tools/console_settings.ts +329 -0
- package/src/tools/delivery_trace.ts +291 -0
- package/src/tools/diagnostics.ts +1557 -0
- package/src/tools/email.ts +486 -0
- package/src/tools/output_schemas.ts +488 -0
- package/src/tools/preflight.ts +768 -0
- package/src/tools/sync.ts +139 -0
- package/src/tools/templates.ts +602 -0
- package/src/tools/tool_utils.ts +341 -0
- package/src/webhook_receiver.ts +467 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,1585 @@
|
|
|
1
|
+
import {
|
|
2
|
+
currentOnBehalfOf,
|
|
3
|
+
currentToolAbortSignal,
|
|
4
|
+
resolveOnBehalfOfHeader,
|
|
5
|
+
} from './tool_signal';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_SENDGRID_BASE = 'https://api.sendgrid.com/v3';
|
|
8
|
+
const MAX_RATE_LIMIT_RETRIES = 3;
|
|
9
|
+
const DEFAULT_RETRY_DELAY_MS = 1000;
|
|
10
|
+
|
|
11
|
+
type QueryParamValue = string | number | boolean | undefined | readonly string[];
|
|
12
|
+
|
|
13
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
14
|
+
return typeof value === 'object' && value !== null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readAsmGroups(value: unknown): AsmGroupSuppression[] {
|
|
18
|
+
const rows = Array.isArray(value)
|
|
19
|
+
? value
|
|
20
|
+
: isObject(value) && Array.isArray(value['suppressions'])
|
|
21
|
+
? value['suppressions']
|
|
22
|
+
: [];
|
|
23
|
+
return rows.filter((row): row is AsmGroupSuppression => {
|
|
24
|
+
if (!isObject(row) || typeof row['id'] !== 'number') return false;
|
|
25
|
+
return typeof row['suppressed'] === 'boolean';
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function coerceArray<T>(value: unknown): T[] {
|
|
30
|
+
if (Array.isArray(value)) return value as T[];
|
|
31
|
+
if (!isObject(value)) return [];
|
|
32
|
+
|
|
33
|
+
const directResult = value['result'];
|
|
34
|
+
if (Array.isArray(directResult)) return directResult as T[];
|
|
35
|
+
|
|
36
|
+
const directResults = value['results'];
|
|
37
|
+
if (Array.isArray(directResults)) return directResults as T[];
|
|
38
|
+
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SendGridErrorDetail {
|
|
43
|
+
message: string;
|
|
44
|
+
field?: string | null;
|
|
45
|
+
help?: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toErrorDetail(value: unknown): SendGridErrorDetail | undefined {
|
|
49
|
+
if (!isObject(value) || typeof value['message'] !== 'string')
|
|
50
|
+
return undefined;
|
|
51
|
+
|
|
52
|
+
const fieldValue = value['field'];
|
|
53
|
+
const field =
|
|
54
|
+
typeof fieldValue === 'string' || fieldValue === null
|
|
55
|
+
? fieldValue
|
|
56
|
+
: undefined;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
message: value['message'],
|
|
60
|
+
field,
|
|
61
|
+
help: value['help'],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseSendGridErrorDetails(rawBody: string): SendGridErrorDetail[] {
|
|
66
|
+
const trimmed = rawBody.trim();
|
|
67
|
+
if (trimmed.length === 0) return [];
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(trimmed) as unknown;
|
|
71
|
+
if (!isObject(parsed)) return [{ message: trimmed }];
|
|
72
|
+
|
|
73
|
+
const errors = parsed['errors'];
|
|
74
|
+
if (Array.isArray(errors)) {
|
|
75
|
+
const mapped = errors
|
|
76
|
+
.map(toErrorDetail)
|
|
77
|
+
.filter((item): item is SendGridErrorDetail => item !== undefined);
|
|
78
|
+
if (mapped.length > 0) return mapped;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const single = toErrorDetail(parsed);
|
|
82
|
+
if (single) return [single];
|
|
83
|
+
} catch {
|
|
84
|
+
return [{ message: trimmed }];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return [{ message: trimmed }];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseRetryAfterMs(value: string | null): number | undefined {
|
|
91
|
+
if (!value) return undefined;
|
|
92
|
+
|
|
93
|
+
const seconds = Number(value);
|
|
94
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
95
|
+
return Math.max(0, Math.round(seconds * 1000));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const dateMs = Date.parse(value);
|
|
99
|
+
if (!Number.isNaN(dateMs)) {
|
|
100
|
+
return Math.max(0, dateMs - Date.now());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export class SendGridApiError extends Error {
|
|
107
|
+
readonly status: number;
|
|
108
|
+
readonly method: string;
|
|
109
|
+
readonly path: string;
|
|
110
|
+
readonly errors: SendGridErrorDetail[];
|
|
111
|
+
readonly rawBody: string;
|
|
112
|
+
|
|
113
|
+
constructor(params: {
|
|
114
|
+
status: number;
|
|
115
|
+
method: string;
|
|
116
|
+
path: string;
|
|
117
|
+
errors: SendGridErrorDetail[];
|
|
118
|
+
rawBody: string;
|
|
119
|
+
}) {
|
|
120
|
+
const lead = params.errors[0]?.message;
|
|
121
|
+
super(
|
|
122
|
+
lead
|
|
123
|
+
? `SendGrid ${params.method} ${params.path} -> ${params.status}: ${lead}`
|
|
124
|
+
: `SendGrid ${params.method} ${params.path} -> ${params.status}`,
|
|
125
|
+
);
|
|
126
|
+
this.name = 'SendGridApiError';
|
|
127
|
+
this.status = params.status;
|
|
128
|
+
this.method = params.method;
|
|
129
|
+
this.path = params.path;
|
|
130
|
+
this.errors = params.errors;
|
|
131
|
+
this.rawBody = params.rawBody;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function isSendGridApiError(error: unknown): error is SendGridApiError {
|
|
136
|
+
return error instanceof SendGridApiError;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface TemplateVersion {
|
|
140
|
+
id: string;
|
|
141
|
+
template_id: string;
|
|
142
|
+
active: 0 | 1;
|
|
143
|
+
name: string;
|
|
144
|
+
subject: string;
|
|
145
|
+
html_content: string;
|
|
146
|
+
plain_content: string;
|
|
147
|
+
generate_plain_content: boolean;
|
|
148
|
+
updated_at: string;
|
|
149
|
+
thumbnail_url?: string;
|
|
150
|
+
test_data?: string;
|
|
151
|
+
editor?: 'code' | 'design';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface Template {
|
|
155
|
+
id: string;
|
|
156
|
+
name: string;
|
|
157
|
+
generation: 'legacy' | 'dynamic';
|
|
158
|
+
updated_at: string;
|
|
159
|
+
versions: TemplateVersion[];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface TemplateListResponse {
|
|
163
|
+
result: Template[];
|
|
164
|
+
_metadata: { self: string; count: number; next?: string };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface UpdateTemplateParams {
|
|
168
|
+
name?: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface SendGridEmailAddress {
|
|
172
|
+
email: string;
|
|
173
|
+
name?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface SendGridMailContent {
|
|
177
|
+
type: string;
|
|
178
|
+
value: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface SendGridMailAttachment {
|
|
182
|
+
content: string;
|
|
183
|
+
filename: string;
|
|
184
|
+
type?: string;
|
|
185
|
+
disposition?: 'attachment' | 'inline';
|
|
186
|
+
content_id?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface SendGridMailPersonalization {
|
|
190
|
+
to: SendGridEmailAddress[];
|
|
191
|
+
cc?: SendGridEmailAddress[];
|
|
192
|
+
bcc?: SendGridEmailAddress[];
|
|
193
|
+
subject?: string;
|
|
194
|
+
dynamic_template_data?: Record<string, unknown>;
|
|
195
|
+
custom_args?: Record<string, string>;
|
|
196
|
+
headers?: Record<string, string>;
|
|
197
|
+
send_at?: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface SendGridMailSendPayload {
|
|
201
|
+
personalizations: SendGridMailPersonalization[];
|
|
202
|
+
from: SendGridEmailAddress;
|
|
203
|
+
reply_to?: SendGridEmailAddress;
|
|
204
|
+
subject?: string;
|
|
205
|
+
content?: SendGridMailContent[];
|
|
206
|
+
attachments?: SendGridMailAttachment[];
|
|
207
|
+
template_id?: string;
|
|
208
|
+
categories?: string[];
|
|
209
|
+
custom_args?: Record<string, string>;
|
|
210
|
+
headers?: Record<string, string>;
|
|
211
|
+
send_at?: number;
|
|
212
|
+
batch_id?: string;
|
|
213
|
+
asm?: {
|
|
214
|
+
group_id: number;
|
|
215
|
+
groups_to_display?: number[];
|
|
216
|
+
};
|
|
217
|
+
ip_pool_name?: string;
|
|
218
|
+
mail_settings?: Record<string, unknown>;
|
|
219
|
+
tracking_settings?: Record<string, unknown>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface SendEmailParams {
|
|
223
|
+
to: string;
|
|
224
|
+
templateId: string;
|
|
225
|
+
dynamicTemplateData: Record<string, unknown>;
|
|
226
|
+
fromEmail: string;
|
|
227
|
+
fromName: string;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export type ScheduledSendStatus = 'pause' | 'cancel';
|
|
231
|
+
|
|
232
|
+
export interface ScheduledSendState {
|
|
233
|
+
batch_id: string;
|
|
234
|
+
status: ScheduledSendStatus;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface AsmGroup {
|
|
238
|
+
id: number;
|
|
239
|
+
name: string;
|
|
240
|
+
description?: string;
|
|
241
|
+
is_default?: boolean;
|
|
242
|
+
unsubscribes?: number;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface DomainWarnList {
|
|
246
|
+
hardFailures: string[];
|
|
247
|
+
softFailures: string[];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface EnforcedTlsSettings {
|
|
251
|
+
require_tls: boolean;
|
|
252
|
+
require_valid_cert: boolean;
|
|
253
|
+
version: number;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface VerifiedSender {
|
|
257
|
+
id: number;
|
|
258
|
+
nickname?: string;
|
|
259
|
+
from_email?: string;
|
|
260
|
+
from_name?: string;
|
|
261
|
+
reply_to?: string;
|
|
262
|
+
verified?: boolean;
|
|
263
|
+
locked?: boolean;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export interface AuthenticatedDomain {
|
|
267
|
+
id: number;
|
|
268
|
+
user_id?: number;
|
|
269
|
+
subdomain: string;
|
|
270
|
+
domain: string;
|
|
271
|
+
username?: string;
|
|
272
|
+
default?: boolean;
|
|
273
|
+
valid?: boolean;
|
|
274
|
+
legacy?: boolean;
|
|
275
|
+
custom_spf?: boolean;
|
|
276
|
+
automatic_security?: boolean;
|
|
277
|
+
dns?: Record<string, unknown>;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface BrandedLink {
|
|
281
|
+
id: number;
|
|
282
|
+
domain: string;
|
|
283
|
+
subdomain: string;
|
|
284
|
+
default?: boolean;
|
|
285
|
+
valid?: boolean;
|
|
286
|
+
legacy?: boolean;
|
|
287
|
+
dns?: Record<string, unknown>;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface SendGridMessageActivity {
|
|
291
|
+
msg_id: string;
|
|
292
|
+
from_email?: string;
|
|
293
|
+
to_email?: string;
|
|
294
|
+
subject?: string;
|
|
295
|
+
status?: string;
|
|
296
|
+
opens_count?: number;
|
|
297
|
+
clicks_count?: number;
|
|
298
|
+
last_event_time?: string;
|
|
299
|
+
last_timestamp?: number;
|
|
300
|
+
[key: string]: unknown;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface SendGridMessageActivityListResponse {
|
|
304
|
+
messages: SendGridMessageActivity[];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export interface EmailLogMessage {
|
|
308
|
+
from_email?: string;
|
|
309
|
+
to_email?: string;
|
|
310
|
+
subject?: string;
|
|
311
|
+
status?: string;
|
|
312
|
+
reason?: string;
|
|
313
|
+
sg_message_id?: string;
|
|
314
|
+
sg_message_id_created_at?: string;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export interface EmailLogListResponse {
|
|
318
|
+
messages: EmailLogMessage[];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface AsmGroupSuppression {
|
|
322
|
+
id: number;
|
|
323
|
+
name: string;
|
|
324
|
+
description?: string;
|
|
325
|
+
is_default?: boolean;
|
|
326
|
+
suppressed: boolean;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export type SuppressionDeleteType =
|
|
330
|
+
| 'bounce'
|
|
331
|
+
| 'block'
|
|
332
|
+
| 'spam_report'
|
|
333
|
+
| 'invalid_email'
|
|
334
|
+
| 'global'
|
|
335
|
+
| 'group';
|
|
336
|
+
|
|
337
|
+
export type StatsDimension =
|
|
338
|
+
| 'global'
|
|
339
|
+
| 'category'
|
|
340
|
+
| 'category_sums'
|
|
341
|
+
| 'mailbox_provider'
|
|
342
|
+
| 'geo'
|
|
343
|
+
| 'browser'
|
|
344
|
+
| 'device'
|
|
345
|
+
| 'client';
|
|
346
|
+
|
|
347
|
+
export type StatsAggregation = 'day' | 'week' | 'month';
|
|
348
|
+
|
|
349
|
+
export interface StatsQuery {
|
|
350
|
+
startDate: string;
|
|
351
|
+
endDate?: string;
|
|
352
|
+
dimension?: StatsDimension;
|
|
353
|
+
aggregatedBy?: StatsAggregation;
|
|
354
|
+
categories?: readonly string[];
|
|
355
|
+
mailboxProviders?: readonly string[];
|
|
356
|
+
country?: string;
|
|
357
|
+
browsers?: readonly string[];
|
|
358
|
+
limit?: number;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface EventWebhookSettings {
|
|
362
|
+
id: string;
|
|
363
|
+
enabled?: boolean;
|
|
364
|
+
url?: string;
|
|
365
|
+
account_status_change?: boolean;
|
|
366
|
+
group_resubscribe?: boolean;
|
|
367
|
+
delivered?: boolean;
|
|
368
|
+
group_unsubscribe?: boolean;
|
|
369
|
+
spam_report?: boolean;
|
|
370
|
+
bounce?: boolean;
|
|
371
|
+
deferred?: boolean;
|
|
372
|
+
unsubscribe?: boolean;
|
|
373
|
+
processed?: boolean;
|
|
374
|
+
open?: boolean;
|
|
375
|
+
click?: boolean;
|
|
376
|
+
dropped?: boolean;
|
|
377
|
+
friendly_name?: string | null;
|
|
378
|
+
oauth_client_id?: string | null;
|
|
379
|
+
oauth_client_secret?: string | null;
|
|
380
|
+
oauth_token_url?: string | null;
|
|
381
|
+
public_key?: string;
|
|
382
|
+
created_date?: string | null;
|
|
383
|
+
updated_date?: string;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export interface EventWebhookSettingsListResponse {
|
|
387
|
+
max_allowed: number;
|
|
388
|
+
webhooks: EventWebhookSettings[];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export interface UpdateEventWebhookPayload {
|
|
392
|
+
enabled?: boolean;
|
|
393
|
+
url?: string;
|
|
394
|
+
account_status_change?: boolean;
|
|
395
|
+
group_resubscribe?: boolean;
|
|
396
|
+
delivered?: boolean;
|
|
397
|
+
group_unsubscribe?: boolean;
|
|
398
|
+
spam_report?: boolean;
|
|
399
|
+
bounce?: boolean;
|
|
400
|
+
deferred?: boolean;
|
|
401
|
+
unsubscribe?: boolean;
|
|
402
|
+
processed?: boolean;
|
|
403
|
+
open?: boolean;
|
|
404
|
+
click?: boolean;
|
|
405
|
+
dropped?: boolean;
|
|
406
|
+
friendly_name?: string | null;
|
|
407
|
+
oauth_client_id?: string | null;
|
|
408
|
+
oauth_client_secret?: string | null;
|
|
409
|
+
oauth_token_url?: string | null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export interface SuppressionEntry {
|
|
413
|
+
email: string;
|
|
414
|
+
created: number;
|
|
415
|
+
reason?: string;
|
|
416
|
+
status?: string;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export type SuppressionListType =
|
|
420
|
+
| 'bounces'
|
|
421
|
+
| 'blocks'
|
|
422
|
+
| 'unsubscribes'
|
|
423
|
+
| 'spam_reports'
|
|
424
|
+
| 'invalid_emails'
|
|
425
|
+
| 'global_unsubscribes';
|
|
426
|
+
|
|
427
|
+
export interface GlobalStats {
|
|
428
|
+
date: string;
|
|
429
|
+
stats: Array<{
|
|
430
|
+
metrics: {
|
|
431
|
+
requests: number;
|
|
432
|
+
delivered: number;
|
|
433
|
+
bounces: number;
|
|
434
|
+
clicks: number;
|
|
435
|
+
opens: number;
|
|
436
|
+
spam_reports: number;
|
|
437
|
+
unsubscribes: number;
|
|
438
|
+
};
|
|
439
|
+
}>;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export interface UserAccount {
|
|
443
|
+
type?: string;
|
|
444
|
+
reputation?: number;
|
|
445
|
+
[key: string]: unknown;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export interface UserProfile {
|
|
449
|
+
company?: string;
|
|
450
|
+
first_name?: string;
|
|
451
|
+
last_name?: string;
|
|
452
|
+
address?: string;
|
|
453
|
+
address2?: string;
|
|
454
|
+
city?: string;
|
|
455
|
+
state?: string;
|
|
456
|
+
zip?: string;
|
|
457
|
+
country?: string;
|
|
458
|
+
phone?: string;
|
|
459
|
+
website?: string;
|
|
460
|
+
[key: string]: unknown;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export interface SendGridSubuser {
|
|
464
|
+
id?: number;
|
|
465
|
+
username: string;
|
|
466
|
+
email?: string;
|
|
467
|
+
disabled?: boolean;
|
|
468
|
+
region?: string;
|
|
469
|
+
[key: string]: unknown;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export interface UserCredits {
|
|
473
|
+
remain?: number;
|
|
474
|
+
total?: number;
|
|
475
|
+
used?: number;
|
|
476
|
+
overage?: number;
|
|
477
|
+
[key: string]: unknown;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export interface MailSettingSummary {
|
|
481
|
+
title?: string;
|
|
482
|
+
name?: string;
|
|
483
|
+
enabled?: boolean;
|
|
484
|
+
[key: string]: unknown;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export interface MailSettingsListResponse {
|
|
488
|
+
result?: MailSettingSummary[];
|
|
489
|
+
[key: string]: unknown;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export type MailSettingName =
|
|
493
|
+
| 'address_whitelist'
|
|
494
|
+
| 'bcc'
|
|
495
|
+
| 'bounce_purge'
|
|
496
|
+
| 'footer'
|
|
497
|
+
| 'forward_bounce'
|
|
498
|
+
| 'forward_spam'
|
|
499
|
+
| 'plain_content'
|
|
500
|
+
| 'spam_check'
|
|
501
|
+
| 'template';
|
|
502
|
+
|
|
503
|
+
export type TrackingSettingName =
|
|
504
|
+
| 'click'
|
|
505
|
+
| 'open'
|
|
506
|
+
| 'subscription'
|
|
507
|
+
| 'google_analytics';
|
|
508
|
+
|
|
509
|
+
export interface TrackingSettingSummary {
|
|
510
|
+
name?: string;
|
|
511
|
+
title?: string;
|
|
512
|
+
description?: string;
|
|
513
|
+
enabled?: boolean;
|
|
514
|
+
[key: string]: unknown;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export interface TrackingSettingsListResponse {
|
|
518
|
+
result?: TrackingSettingSummary[];
|
|
519
|
+
[key: string]: unknown;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export interface SendGridAlert {
|
|
523
|
+
id: number;
|
|
524
|
+
type: 'usage_limit' | 'stats_notification' | string;
|
|
525
|
+
email_to?: string;
|
|
526
|
+
frequency?: 'daily' | 'weekly' | 'monthly' | string;
|
|
527
|
+
percentage?: number;
|
|
528
|
+
created_at?: number;
|
|
529
|
+
updated_at?: number;
|
|
530
|
+
[key: string]: unknown;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export interface InboundParseSetting {
|
|
534
|
+
url: string;
|
|
535
|
+
hostname: string;
|
|
536
|
+
spam_check?: boolean;
|
|
537
|
+
send_raw?: boolean;
|
|
538
|
+
[key: string]: unknown;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export interface CreateVerifiedSenderPayload {
|
|
542
|
+
nickname: string;
|
|
543
|
+
from_email: string;
|
|
544
|
+
reply_to: string;
|
|
545
|
+
from_name?: string;
|
|
546
|
+
reply_to_name?: string;
|
|
547
|
+
address?: string;
|
|
548
|
+
address2?: string;
|
|
549
|
+
state?: string;
|
|
550
|
+
city?: string;
|
|
551
|
+
country?: string;
|
|
552
|
+
zip?: string;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export interface CreateAuthenticatedDomainPayload {
|
|
556
|
+
domain: string;
|
|
557
|
+
subdomain?: string;
|
|
558
|
+
username?: string;
|
|
559
|
+
ips?: string[];
|
|
560
|
+
custom_spf?: boolean;
|
|
561
|
+
default?: boolean;
|
|
562
|
+
automatic_security?: boolean;
|
|
563
|
+
custom_dkim_selector?: string;
|
|
564
|
+
region?: 'global' | 'eu';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export interface UpdateBrandedLinkPayload {
|
|
568
|
+
default?: boolean;
|
|
569
|
+
subdomain?: string;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export interface CreateAlertPayload {
|
|
573
|
+
type: 'usage_limit' | 'stats_notification';
|
|
574
|
+
email_to?: string;
|
|
575
|
+
frequency?: 'daily' | 'weekly' | 'monthly';
|
|
576
|
+
percentage?: number;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export interface UpdateAlertPayload {
|
|
580
|
+
type?: 'usage_limit' | 'stats_notification';
|
|
581
|
+
email_to?: string;
|
|
582
|
+
frequency?: 'daily' | 'weekly' | 'monthly';
|
|
583
|
+
percentage?: number;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export interface CreateInboundParsePayload {
|
|
587
|
+
url: string;
|
|
588
|
+
hostname: string;
|
|
589
|
+
spam_check?: boolean;
|
|
590
|
+
send_raw?: boolean;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export interface UpdateInboundParsePayload {
|
|
594
|
+
url?: string;
|
|
595
|
+
hostname?: string;
|
|
596
|
+
spam_check?: boolean;
|
|
597
|
+
send_raw?: boolean;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export class SendGridClient {
|
|
601
|
+
private parseNextPageToken(metadataNext?: string): string | undefined {
|
|
602
|
+
if (!metadataNext) return undefined;
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
const nextUrl = new URL(metadataNext, this.baseUrl);
|
|
606
|
+
return nextUrl.searchParams.get('page_token') ?? undefined;
|
|
607
|
+
} catch {
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
private readonly apiKey: string;
|
|
613
|
+
private readonly baseUrl: string;
|
|
614
|
+
private readonly onBehalfOf?: string;
|
|
615
|
+
readonly preferEmailLogs: boolean;
|
|
616
|
+
|
|
617
|
+
constructor(
|
|
618
|
+
apiKey: string,
|
|
619
|
+
baseUrl = DEFAULT_SENDGRID_BASE,
|
|
620
|
+
options?: { onBehalfOf?: string; preferEmailLogs?: boolean },
|
|
621
|
+
) {
|
|
622
|
+
this.apiKey = apiKey;
|
|
623
|
+
this.baseUrl = baseUrl.replace(/\/+$/u, '');
|
|
624
|
+
const onBehalfOf = options?.onBehalfOf?.trim();
|
|
625
|
+
this.onBehalfOf = onBehalfOf ? onBehalfOf : undefined;
|
|
626
|
+
this.preferEmailLogs = options?.preferEmailLogs ?? false;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
private buildUrl(
|
|
630
|
+
path: string,
|
|
631
|
+
params?: Record<string, QueryParamValue>,
|
|
632
|
+
): URL {
|
|
633
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
634
|
+
if (!params) return url;
|
|
635
|
+
|
|
636
|
+
for (const [key, value] of Object.entries(params)) {
|
|
637
|
+
if (value === undefined) continue;
|
|
638
|
+
if (Array.isArray(value)) {
|
|
639
|
+
for (const item of value) url.searchParams.append(key, String(item));
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
url.searchParams.set(key, String(value));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return url;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private async requestRaw(
|
|
649
|
+
method: string,
|
|
650
|
+
path: string,
|
|
651
|
+
body?: unknown,
|
|
652
|
+
params?: Record<string, QueryParamValue>,
|
|
653
|
+
): Promise<Response> {
|
|
654
|
+
const url = this.buildUrl(path, params);
|
|
655
|
+
|
|
656
|
+
for (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {
|
|
657
|
+
const signal = currentToolAbortSignal();
|
|
658
|
+
if (signal?.aborted) {
|
|
659
|
+
throw new DOMException('The MCP client aborted the tool call.', 'AbortError');
|
|
660
|
+
}
|
|
661
|
+
const onBehalfOf = resolveOnBehalfOfHeader(
|
|
662
|
+
currentOnBehalfOf(),
|
|
663
|
+
this.onBehalfOf,
|
|
664
|
+
);
|
|
665
|
+
const res = await fetch(url.toString(), {
|
|
666
|
+
method,
|
|
667
|
+
headers: {
|
|
668
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
669
|
+
'Content-Type': 'application/json',
|
|
670
|
+
...(onBehalfOf ? { 'on-behalf-of': onBehalfOf } : {}),
|
|
671
|
+
},
|
|
672
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
673
|
+
signal,
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
if (res.ok) return res;
|
|
677
|
+
|
|
678
|
+
if (res.status === 429 && attempt < MAX_RATE_LIMIT_RETRIES) {
|
|
679
|
+
const retryDelay =
|
|
680
|
+
parseRetryAfterMs(res.headers.get('retry-after')) ??
|
|
681
|
+
DEFAULT_RETRY_DELAY_MS * (attempt + 1);
|
|
682
|
+
await Bun.sleep(retryDelay);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const rawBody = await res.text();
|
|
687
|
+
throw new SendGridApiError({
|
|
688
|
+
status: res.status,
|
|
689
|
+
method,
|
|
690
|
+
path,
|
|
691
|
+
errors: parseSendGridErrorDetails(rawBody),
|
|
692
|
+
rawBody,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
throw new SendGridApiError({
|
|
697
|
+
status: 429,
|
|
698
|
+
method,
|
|
699
|
+
path,
|
|
700
|
+
errors: [{ message: 'Rate limit retry attempts exhausted.' }],
|
|
701
|
+
rawBody: '',
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
private async request<T>(
|
|
706
|
+
method: string,
|
|
707
|
+
path: string,
|
|
708
|
+
body?: unknown,
|
|
709
|
+
params?: Record<string, QueryParamValue>,
|
|
710
|
+
): Promise<T> {
|
|
711
|
+
const res = await this.requestRaw(method, path, body, params);
|
|
712
|
+
|
|
713
|
+
if (res.status === 204) return {} as T;
|
|
714
|
+
|
|
715
|
+
const rawBody = await res.text();
|
|
716
|
+
if (rawBody.trim().length === 0) return {} as T;
|
|
717
|
+
|
|
718
|
+
try {
|
|
719
|
+
return JSON.parse(rawBody) as T;
|
|
720
|
+
} catch {
|
|
721
|
+
throw new SendGridApiError({
|
|
722
|
+
status: res.status,
|
|
723
|
+
method,
|
|
724
|
+
path,
|
|
725
|
+
errors: [
|
|
726
|
+
{ message: 'Expected JSON response but received plain text.' },
|
|
727
|
+
],
|
|
728
|
+
rawBody,
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// ─── Templates ──────────────────────────────────────────────────────────────
|
|
734
|
+
|
|
735
|
+
listTemplates(
|
|
736
|
+
pageSize = 50,
|
|
737
|
+
pageToken?: string,
|
|
738
|
+
): Promise<TemplateListResponse> {
|
|
739
|
+
return this.request<TemplateListResponse>('GET', '/templates', undefined, {
|
|
740
|
+
generations: 'dynamic',
|
|
741
|
+
page_size: pageSize,
|
|
742
|
+
page_token: pageToken,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async listAllDynamicTemplates(pageSize = 200): Promise<Template[]> {
|
|
747
|
+
const all: Template[] = [];
|
|
748
|
+
let nextPageToken: string | undefined;
|
|
749
|
+
|
|
750
|
+
do {
|
|
751
|
+
const page = await this.listTemplates(pageSize, nextPageToken);
|
|
752
|
+
all.push(...(page.result ?? []));
|
|
753
|
+
nextPageToken = this.parseNextPageToken(page._metadata?.next);
|
|
754
|
+
} while (nextPageToken);
|
|
755
|
+
|
|
756
|
+
return all;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
getTemplate(templateId: string): Promise<Template> {
|
|
760
|
+
return this.request<Template>('GET', `/templates/${templateId}`);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
updateTemplate(
|
|
764
|
+
templateId: string,
|
|
765
|
+
params: UpdateTemplateParams,
|
|
766
|
+
): Promise<Template> {
|
|
767
|
+
const body: Record<string, unknown> = {};
|
|
768
|
+
if (params.name !== undefined) body['name'] = params.name;
|
|
769
|
+
return this.request<Template>('PATCH', `/templates/${templateId}`, body);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
getTemplateVersion(
|
|
773
|
+
templateId: string,
|
|
774
|
+
versionId: string,
|
|
775
|
+
): Promise<TemplateVersion> {
|
|
776
|
+
return this.request<TemplateVersion>(
|
|
777
|
+
'GET',
|
|
778
|
+
`/templates/${templateId}/versions/${versionId}`,
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
createTemplate(name: string): Promise<Template> {
|
|
783
|
+
return this.request<Template>('POST', '/templates', {
|
|
784
|
+
name,
|
|
785
|
+
generation: 'dynamic',
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
createTemplateVersion(
|
|
790
|
+
templateId: string,
|
|
791
|
+
params: {
|
|
792
|
+
name: string;
|
|
793
|
+
subject: string;
|
|
794
|
+
htmlContent: string;
|
|
795
|
+
active?: 0 | 1;
|
|
796
|
+
},
|
|
797
|
+
): Promise<TemplateVersion> {
|
|
798
|
+
return this.request<TemplateVersion>(
|
|
799
|
+
'POST',
|
|
800
|
+
`/templates/${templateId}/versions`,
|
|
801
|
+
{
|
|
802
|
+
name: params.name,
|
|
803
|
+
subject: params.subject,
|
|
804
|
+
html_content: params.htmlContent,
|
|
805
|
+
generate_plain_content: true,
|
|
806
|
+
active: params.active ?? 1,
|
|
807
|
+
editor: 'code',
|
|
808
|
+
},
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
updateTemplateVersion(
|
|
813
|
+
templateId: string,
|
|
814
|
+
versionId: string,
|
|
815
|
+
params: Partial<{
|
|
816
|
+
name: string;
|
|
817
|
+
subject: string;
|
|
818
|
+
htmlContent: string;
|
|
819
|
+
active: 0 | 1;
|
|
820
|
+
testData: string;
|
|
821
|
+
}>,
|
|
822
|
+
): Promise<TemplateVersion> {
|
|
823
|
+
const body: Record<string, unknown> = {};
|
|
824
|
+
if (params.name !== undefined) body['name'] = params.name;
|
|
825
|
+
if (params.subject !== undefined) body['subject'] = params.subject;
|
|
826
|
+
if (params.htmlContent !== undefined)
|
|
827
|
+
body['html_content'] = params.htmlContent;
|
|
828
|
+
if (params.active !== undefined) body['active'] = params.active;
|
|
829
|
+
if (params.testData !== undefined) body['test_data'] = params.testData;
|
|
830
|
+
|
|
831
|
+
return this.request<TemplateVersion>(
|
|
832
|
+
'PATCH',
|
|
833
|
+
`/templates/${templateId}/versions/${versionId}`,
|
|
834
|
+
body,
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
activateTemplateVersion(
|
|
839
|
+
templateId: string,
|
|
840
|
+
versionId: string,
|
|
841
|
+
): Promise<TemplateVersion> {
|
|
842
|
+
return this.request<TemplateVersion>(
|
|
843
|
+
'POST',
|
|
844
|
+
`/templates/${templateId}/versions/${versionId}/activate`,
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
deleteTemplateVersion(templateId: string, versionId: string): Promise<void> {
|
|
849
|
+
return this.request<void>(
|
|
850
|
+
'DELETE',
|
|
851
|
+
`/templates/${templateId}/versions/${versionId}`,
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
deleteTemplate(templateId: string): Promise<void> {
|
|
856
|
+
return this.request<void>('DELETE', `/templates/${templateId}`);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// ─── Sending & Scheduling ───────────────────────────────────────────────────
|
|
860
|
+
|
|
861
|
+
async sendMail(
|
|
862
|
+
payload: SendGridMailSendPayload,
|
|
863
|
+
): Promise<{ messageId: string; statusCode: number }> {
|
|
864
|
+
const res = await this.requestRaw('POST', '/mail/send', payload);
|
|
865
|
+
return {
|
|
866
|
+
statusCode: res.status,
|
|
867
|
+
messageId: res.headers.get('x-message-id') ?? '',
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
async sendEmail(params: SendEmailParams): Promise<{ messageId: string }> {
|
|
872
|
+
const result = await this.sendMail({
|
|
873
|
+
personalizations: [
|
|
874
|
+
{
|
|
875
|
+
to: [{ email: params.to }],
|
|
876
|
+
dynamic_template_data: params.dynamicTemplateData,
|
|
877
|
+
},
|
|
878
|
+
],
|
|
879
|
+
from: { email: params.fromEmail, name: params.fromName },
|
|
880
|
+
template_id: params.templateId,
|
|
881
|
+
});
|
|
882
|
+
return { messageId: result.messageId };
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
createBatchId(): Promise<{ batch_id: string }> {
|
|
886
|
+
return this.request<{ batch_id: string }>('POST', '/mail/batch');
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
async upsertScheduledSend(
|
|
890
|
+
batchId: string,
|
|
891
|
+
status: ScheduledSendStatus,
|
|
892
|
+
): Promise<ScheduledSendState> {
|
|
893
|
+
await this.request<void>(
|
|
894
|
+
'PATCH',
|
|
895
|
+
`/user/scheduled_sends/${encodeURIComponent(batchId)}`,
|
|
896
|
+
{ status },
|
|
897
|
+
);
|
|
898
|
+
return { batch_id: batchId, status };
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
async getScheduledSend(batchId: string): Promise<ScheduledSendState> {
|
|
902
|
+
const payload = await this.request<unknown>(
|
|
903
|
+
'GET',
|
|
904
|
+
`/user/scheduled_sends/${encodeURIComponent(batchId)}`,
|
|
905
|
+
);
|
|
906
|
+
const rows = Array.isArray(payload) ? payload : [payload];
|
|
907
|
+
for (const row of rows) {
|
|
908
|
+
if (!isObject(row)) continue;
|
|
909
|
+
if (row['batch_id'] !== batchId) continue;
|
|
910
|
+
if (row['status'] !== 'pause' && row['status'] !== 'cancel') continue;
|
|
911
|
+
return {
|
|
912
|
+
batch_id: batchId,
|
|
913
|
+
status: row['status'],
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
throw new SendGridApiError({
|
|
917
|
+
status: 404,
|
|
918
|
+
method: 'GET',
|
|
919
|
+
path: `/user/scheduled_sends/${batchId}`,
|
|
920
|
+
errors: [
|
|
921
|
+
{
|
|
922
|
+
message:
|
|
923
|
+
'No pause or cancel exists for this batch_id. SendGrid returns 200 and an empty array, not 404.',
|
|
924
|
+
},
|
|
925
|
+
],
|
|
926
|
+
rawBody: JSON.stringify(payload),
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
async listScheduledSends(): Promise<ScheduledSendState[]> {
|
|
931
|
+
const payload = await this.request<unknown>('GET', '/user/scheduled_sends');
|
|
932
|
+
return Array.isArray(payload) ? (payload as ScheduledSendState[]) : [];
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async resumeScheduledSend(batchId: string): Promise<void> {
|
|
936
|
+
await this.request<void>(
|
|
937
|
+
'DELETE',
|
|
938
|
+
`/user/scheduled_sends/${encodeURIComponent(batchId)}`,
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// ─── Sender / Account Auth Surfaces ─────────────────────────────────────────
|
|
943
|
+
|
|
944
|
+
async listVerifiedSenders(params?: {
|
|
945
|
+
limit?: number;
|
|
946
|
+
lastSeenID?: number;
|
|
947
|
+
id?: number;
|
|
948
|
+
}): Promise<VerifiedSender[]> {
|
|
949
|
+
const payload = await this.request<unknown>(
|
|
950
|
+
'GET',
|
|
951
|
+
'/verified_senders',
|
|
952
|
+
undefined,
|
|
953
|
+
{
|
|
954
|
+
limit: params?.limit,
|
|
955
|
+
lastSeenID: params?.lastSeenID,
|
|
956
|
+
id: params?.id,
|
|
957
|
+
},
|
|
958
|
+
);
|
|
959
|
+
return coerceArray<VerifiedSender>(payload);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async listAuthenticatedDomains(): Promise<AuthenticatedDomain[]> {
|
|
963
|
+
const payload = await this.request<unknown>('GET', '/whitelabel/domains');
|
|
964
|
+
return coerceArray<AuthenticatedDomain>(payload);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
async listBrandedLinks(): Promise<BrandedLink[]> {
|
|
968
|
+
const payload = await this.request<unknown>('GET', '/whitelabel/links');
|
|
969
|
+
return coerceArray<BrandedLink>(payload);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// ─── Email Activity & Event Webhooks ────────────────────────────────────────
|
|
973
|
+
|
|
974
|
+
filterMessages(
|
|
975
|
+
query: string,
|
|
976
|
+
limit = 25,
|
|
977
|
+
): Promise<SendGridMessageActivityListResponse> {
|
|
978
|
+
return this.request<SendGridMessageActivityListResponse>(
|
|
979
|
+
'GET',
|
|
980
|
+
'/messages',
|
|
981
|
+
undefined,
|
|
982
|
+
{
|
|
983
|
+
query,
|
|
984
|
+
limit,
|
|
985
|
+
},
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
getMessageById(msgId: string): Promise<SendGridMessageActivity> {
|
|
990
|
+
return this.request<SendGridMessageActivity>(
|
|
991
|
+
'GET',
|
|
992
|
+
`/messages/${encodeURIComponent(msgId)}`,
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
getAllEventWebhooks(
|
|
997
|
+
includeAccountStatusChange = false,
|
|
998
|
+
): Promise<EventWebhookSettingsListResponse> {
|
|
999
|
+
return this.request<EventWebhookSettingsListResponse>(
|
|
1000
|
+
'GET',
|
|
1001
|
+
'/user/webhooks/event/settings/all',
|
|
1002
|
+
undefined,
|
|
1003
|
+
{
|
|
1004
|
+
include: includeAccountStatusChange
|
|
1005
|
+
? 'account_status_change'
|
|
1006
|
+
: undefined,
|
|
1007
|
+
},
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
getEventWebhook(
|
|
1012
|
+
id: string,
|
|
1013
|
+
includeAccountStatusChange = false,
|
|
1014
|
+
): Promise<EventWebhookSettings> {
|
|
1015
|
+
return this.request<EventWebhookSettings>(
|
|
1016
|
+
'GET',
|
|
1017
|
+
`/user/webhooks/event/settings/${encodeURIComponent(id)}`,
|
|
1018
|
+
undefined,
|
|
1019
|
+
{
|
|
1020
|
+
include: includeAccountStatusChange
|
|
1021
|
+
? 'account_status_change'
|
|
1022
|
+
: undefined,
|
|
1023
|
+
},
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
updateEventWebhook(
|
|
1028
|
+
id: string,
|
|
1029
|
+
payload: UpdateEventWebhookPayload,
|
|
1030
|
+
includeAccountStatusChange = false,
|
|
1031
|
+
): Promise<EventWebhookSettings> {
|
|
1032
|
+
return this.request<EventWebhookSettings>(
|
|
1033
|
+
'PATCH',
|
|
1034
|
+
`/user/webhooks/event/settings/${encodeURIComponent(id)}`,
|
|
1035
|
+
payload,
|
|
1036
|
+
{
|
|
1037
|
+
include: includeAccountStatusChange
|
|
1038
|
+
? 'account_status_change'
|
|
1039
|
+
: undefined,
|
|
1040
|
+
},
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
toggleEventWebhookSignatureVerification(
|
|
1045
|
+
id: string,
|
|
1046
|
+
enabled: boolean,
|
|
1047
|
+
): Promise<{ id: string; public_key: string }> {
|
|
1048
|
+
return this.request<{ id: string; public_key: string }>(
|
|
1049
|
+
'PATCH',
|
|
1050
|
+
`/user/webhooks/event/settings/signed/${encodeURIComponent(id)}`,
|
|
1051
|
+
{ enabled },
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
createEventWebhook(
|
|
1056
|
+
payload: UpdateEventWebhookPayload & { url: string },
|
|
1057
|
+
): Promise<EventWebhookSettings> {
|
|
1058
|
+
return this.request<EventWebhookSettings>(
|
|
1059
|
+
'POST',
|
|
1060
|
+
'/user/webhooks/event/settings',
|
|
1061
|
+
payload,
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
deleteEventWebhook(id: string): Promise<void> {
|
|
1066
|
+
return this.request<void>(
|
|
1067
|
+
'DELETE',
|
|
1068
|
+
`/user/webhooks/event/settings/${encodeURIComponent(id)}`,
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
testEventWebhook(payload: {
|
|
1073
|
+
url: string;
|
|
1074
|
+
id?: string;
|
|
1075
|
+
}): Promise<Record<string, unknown>> {
|
|
1076
|
+
return this.request<Record<string, unknown>>(
|
|
1077
|
+
'POST',
|
|
1078
|
+
'/user/webhooks/event/test',
|
|
1079
|
+
payload,
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
getPartnerAccountState(accountId: string): Promise<{ state: string }> {
|
|
1084
|
+
return this.request<{ state: string }>(
|
|
1085
|
+
'GET',
|
|
1086
|
+
`/partners/accounts/${encodeURIComponent(accountId)}/state`,
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// ─── Suppressions & Delivery Diagnostics ────────────────────────────────────
|
|
1091
|
+
|
|
1092
|
+
private suppressionPath(type: SuppressionListType): string {
|
|
1093
|
+
if (type === 'global_unsubscribes') return '/suppression/unsubscribes';
|
|
1094
|
+
return `/suppression/${type}`;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
listSuppressions(
|
|
1098
|
+
type: SuppressionListType,
|
|
1099
|
+
params?: {
|
|
1100
|
+
limit?: number;
|
|
1101
|
+
offset?: number;
|
|
1102
|
+
startTime?: number;
|
|
1103
|
+
endTime?: number;
|
|
1104
|
+
email?: string;
|
|
1105
|
+
},
|
|
1106
|
+
): Promise<SuppressionEntry[]> {
|
|
1107
|
+
return this.request<SuppressionEntry[]>(
|
|
1108
|
+
'GET',
|
|
1109
|
+
this.suppressionPath(type),
|
|
1110
|
+
undefined,
|
|
1111
|
+
{
|
|
1112
|
+
limit: params?.limit,
|
|
1113
|
+
offset: params?.offset,
|
|
1114
|
+
start_time: params?.startTime,
|
|
1115
|
+
end_time: params?.endTime,
|
|
1116
|
+
email: params?.email,
|
|
1117
|
+
},
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
async checkSuppression(email: string): Promise<{
|
|
1122
|
+
bounced: boolean;
|
|
1123
|
+
blocked: boolean;
|
|
1124
|
+
unsubscribed: boolean;
|
|
1125
|
+
spamReported: boolean;
|
|
1126
|
+
invalidEmail: boolean;
|
|
1127
|
+
groupUnsubscribed: boolean;
|
|
1128
|
+
groupSuppressions: AsmGroupSuppression[];
|
|
1129
|
+
groupLookupError?: string;
|
|
1130
|
+
details: Record<string, unknown>;
|
|
1131
|
+
}> {
|
|
1132
|
+
const encoded = encodeURIComponent(email);
|
|
1133
|
+
const [bounces, blocks, globalUnsubscribe, spam, invalidEmails, groups] =
|
|
1134
|
+
await Promise.allSettled([
|
|
1135
|
+
this.request<SuppressionEntry[]>(
|
|
1136
|
+
'GET',
|
|
1137
|
+
`/suppression/bounces/${encoded}`,
|
|
1138
|
+
),
|
|
1139
|
+
this.request<SuppressionEntry[]>('GET', `/suppression/blocks/${encoded}`),
|
|
1140
|
+
this.request<Record<string, unknown>>(
|
|
1141
|
+
'GET',
|
|
1142
|
+
`/asm/suppressions/global/${encoded}`,
|
|
1143
|
+
),
|
|
1144
|
+
this.request<SuppressionEntry[]>(
|
|
1145
|
+
'GET',
|
|
1146
|
+
`/suppression/spam_reports/${encoded}`,
|
|
1147
|
+
),
|
|
1148
|
+
this.request<SuppressionEntry[]>(
|
|
1149
|
+
'GET',
|
|
1150
|
+
`/suppression/invalid_emails/${encoded}`,
|
|
1151
|
+
),
|
|
1152
|
+
this.request<unknown>('GET', `/asm/suppressions/${encoded}`),
|
|
1153
|
+
]);
|
|
1154
|
+
|
|
1155
|
+
const get = (result: PromiseSettledResult<SuppressionEntry[]>) =>
|
|
1156
|
+
result.status === 'fulfilled' ? result.value : [];
|
|
1157
|
+
const getObject = (result: PromiseSettledResult<Record<string, unknown>>) =>
|
|
1158
|
+
result.status === 'fulfilled' ? result.value : {};
|
|
1159
|
+
const global = getObject(globalUnsubscribe);
|
|
1160
|
+
const globallyUnsubscribed =
|
|
1161
|
+
typeof global['recipient_email'] === 'string' ||
|
|
1162
|
+
Object.keys(global).length > 0;
|
|
1163
|
+
const groupPayload =
|
|
1164
|
+
groups.status === 'fulfilled' ? readAsmGroups(groups.value) : [];
|
|
1165
|
+
const groupLookupError =
|
|
1166
|
+
groups.status === 'rejected' ? String(groups.reason) : undefined;
|
|
1167
|
+
const suppressedGroups = groupPayload.filter((group) => group.suppressed);
|
|
1168
|
+
|
|
1169
|
+
return {
|
|
1170
|
+
bounced: get(bounces).length > 0,
|
|
1171
|
+
blocked: get(blocks).length > 0,
|
|
1172
|
+
unsubscribed: globallyUnsubscribed,
|
|
1173
|
+
spamReported: get(spam).length > 0,
|
|
1174
|
+
invalidEmail: get(invalidEmails).length > 0,
|
|
1175
|
+
groupUnsubscribed: suppressedGroups.length > 0,
|
|
1176
|
+
groupSuppressions: groupPayload,
|
|
1177
|
+
...(groupLookupError ? { groupLookupError } : {}),
|
|
1178
|
+
details: {
|
|
1179
|
+
bounces: get(bounces),
|
|
1180
|
+
blocks: get(blocks),
|
|
1181
|
+
globalUnsubscribe: global,
|
|
1182
|
+
spamReports: get(spam),
|
|
1183
|
+
invalidEmails: get(invalidEmails),
|
|
1184
|
+
asmGroups: groupPayload,
|
|
1185
|
+
},
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
deleteSuppression(params: {
|
|
1190
|
+
type: SuppressionDeleteType;
|
|
1191
|
+
email: string;
|
|
1192
|
+
groupId?: number;
|
|
1193
|
+
}): Promise<void> {
|
|
1194
|
+
const email = encodeURIComponent(params.email);
|
|
1195
|
+
if (params.type === 'group') {
|
|
1196
|
+
if (params.groupId === undefined) {
|
|
1197
|
+
throw new Error('groupId is required to delete a group suppression');
|
|
1198
|
+
}
|
|
1199
|
+
return this.request<void>(
|
|
1200
|
+
'DELETE',
|
|
1201
|
+
`/asm/groups/${params.groupId}/suppressions/${email}`,
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
const pathByType: Record<Exclude<SuppressionDeleteType, 'group'>, string> =
|
|
1205
|
+
{
|
|
1206
|
+
bounce: `/suppression/bounces/${email}`,
|
|
1207
|
+
block: `/suppression/blocks/${email}`,
|
|
1208
|
+
spam_report: `/suppression/spam_reports/${email}`,
|
|
1209
|
+
invalid_email: `/suppression/invalid_emails/${email}`,
|
|
1210
|
+
global: `/asm/suppressions/global/${email}`,
|
|
1211
|
+
};
|
|
1212
|
+
return this.request<void>('DELETE', pathByType[params.type]);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
searchEmailLogs(params: {
|
|
1216
|
+
query?: string;
|
|
1217
|
+
limit?: number;
|
|
1218
|
+
subusers?: readonly string[];
|
|
1219
|
+
}): Promise<EmailLogListResponse> {
|
|
1220
|
+
return this.request<EmailLogListResponse>('POST', '/logs', {
|
|
1221
|
+
...(params.query ? { query: params.query } : {}),
|
|
1222
|
+
...(params.limit !== undefined ? { limit: params.limit } : {}),
|
|
1223
|
+
...(params.subusers && params.subusers.length > 0
|
|
1224
|
+
? { subusers: [...params.subusers] }
|
|
1225
|
+
: {}),
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
getScopes(): Promise<{ scopes: string[] }> {
|
|
1230
|
+
return this.request<{ scopes: string[] }>('GET', '/scopes');
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async listAsmGroups(): Promise<AsmGroup[]> {
|
|
1234
|
+
const payload = await this.request<unknown>('GET', '/asm/groups');
|
|
1235
|
+
return coerceArray<AsmGroup>(payload);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
createAsmGroup(payload: {
|
|
1239
|
+
name: string;
|
|
1240
|
+
description: string;
|
|
1241
|
+
is_default?: boolean;
|
|
1242
|
+
}): Promise<AsmGroup> {
|
|
1243
|
+
return this.request<AsmGroup>('POST', '/asm/groups', payload);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
async listCategories(params?: {
|
|
1247
|
+
limit?: number;
|
|
1248
|
+
offset?: number;
|
|
1249
|
+
category?: string;
|
|
1250
|
+
}): Promise<string[]> {
|
|
1251
|
+
const payload = await this.request<unknown>('GET', '/categories', undefined, {
|
|
1252
|
+
limit: params?.limit,
|
|
1253
|
+
offset: params?.offset,
|
|
1254
|
+
category: params?.category,
|
|
1255
|
+
});
|
|
1256
|
+
if (!Array.isArray(payload)) return [];
|
|
1257
|
+
return payload.flatMap((row) => {
|
|
1258
|
+
if (typeof row === 'string') return [row];
|
|
1259
|
+
if (
|
|
1260
|
+
row &&
|
|
1261
|
+
typeof row === 'object' &&
|
|
1262
|
+
typeof (row as { category?: unknown }).category === 'string'
|
|
1263
|
+
) {
|
|
1264
|
+
return [(row as { category: string }).category];
|
|
1265
|
+
}
|
|
1266
|
+
return [];
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
async listDomainWarnList(): Promise<DomainWarnList> {
|
|
1271
|
+
const payload = await this.request<{
|
|
1272
|
+
results?: { hard_failures?: unknown; soft_failures?: unknown };
|
|
1273
|
+
}>('GET', '/verified_senders/domains');
|
|
1274
|
+
const names = (value: unknown): string[] =>
|
|
1275
|
+
Array.isArray(value)
|
|
1276
|
+
? value.filter((item): item is string => typeof item === 'string')
|
|
1277
|
+
: [];
|
|
1278
|
+
return {
|
|
1279
|
+
hardFailures: names(payload.results?.hard_failures),
|
|
1280
|
+
softFailures: names(payload.results?.soft_failures),
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
getEnforcedTls(): Promise<EnforcedTlsSettings> {
|
|
1285
|
+
return this.request<EnforcedTlsSettings>('GET', '/user/settings/enforced_tls');
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
async listSubusers(params?: {
|
|
1289
|
+
username?: string;
|
|
1290
|
+
limit?: number;
|
|
1291
|
+
offset?: number;
|
|
1292
|
+
region?: 'all' | 'global' | 'eu';
|
|
1293
|
+
includeRegion?: boolean;
|
|
1294
|
+
}): Promise<SendGridSubuser[]> {
|
|
1295
|
+
const payload = await this.request<unknown>('GET', '/subusers', undefined, {
|
|
1296
|
+
username: params?.username,
|
|
1297
|
+
limit: params?.limit,
|
|
1298
|
+
offset: params?.offset,
|
|
1299
|
+
region: params?.region,
|
|
1300
|
+
include_region: params?.includeRegion,
|
|
1301
|
+
});
|
|
1302
|
+
return coerceArray<SendGridSubuser>(payload);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
getStats(startDate: string, endDate?: string): Promise<GlobalStats[]>;
|
|
1306
|
+
getStats(params: StatsQuery): Promise<unknown>;
|
|
1307
|
+
getStats(
|
|
1308
|
+
startDateOrParams: string | StatsQuery,
|
|
1309
|
+
endDate?: string,
|
|
1310
|
+
): Promise<GlobalStats[] | unknown> {
|
|
1311
|
+
if (typeof startDateOrParams === 'string') {
|
|
1312
|
+
return this.request<GlobalStats[]>('GET', '/stats', undefined, {
|
|
1313
|
+
start_date: startDateOrParams,
|
|
1314
|
+
aggregated_by: 'day',
|
|
1315
|
+
end_date: endDate,
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
const dimension = startDateOrParams.dimension ?? 'global';
|
|
1320
|
+
if (
|
|
1321
|
+
dimension === 'category' &&
|
|
1322
|
+
(startDateOrParams.categories?.length ?? 0) === 0
|
|
1323
|
+
) {
|
|
1324
|
+
throw new Error(
|
|
1325
|
+
'dimension=category requires categories. GET /v3/categories/stats does not return every category unless they are named.',
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
const pathByDimension: Record<StatsDimension, string> = {
|
|
1330
|
+
global: '/stats',
|
|
1331
|
+
category: '/categories/stats',
|
|
1332
|
+
category_sums: '/categories/stats/sums',
|
|
1333
|
+
mailbox_provider: '/mailbox_providers/stats',
|
|
1334
|
+
geo: '/geo/stats',
|
|
1335
|
+
browser: '/browsers/stats',
|
|
1336
|
+
device: '/devices/stats',
|
|
1337
|
+
client: '/clients/stats',
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
return this.request<unknown>('GET', pathByDimension[dimension], undefined, {
|
|
1341
|
+
start_date: startDateOrParams.startDate,
|
|
1342
|
+
end_date: startDateOrParams.endDate,
|
|
1343
|
+
aggregated_by: startDateOrParams.aggregatedBy ?? 'day',
|
|
1344
|
+
categories:
|
|
1345
|
+
dimension === 'category' ? startDateOrParams.categories : undefined,
|
|
1346
|
+
mailbox_providers:
|
|
1347
|
+
dimension === 'mailbox_provider'
|
|
1348
|
+
? startDateOrParams.mailboxProviders
|
|
1349
|
+
: undefined,
|
|
1350
|
+
country: dimension === 'geo' ? startDateOrParams.country : undefined,
|
|
1351
|
+
browsers: dimension === 'browser' ? startDateOrParams.browsers : undefined,
|
|
1352
|
+
limit: startDateOrParams.limit,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// ─── Account & User ─────────────────────────────────────────────────────────
|
|
1357
|
+
|
|
1358
|
+
getUserAccount(): Promise<UserAccount> {
|
|
1359
|
+
return this.request<UserAccount>('GET', '/user/account');
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
getUserProfile(): Promise<UserProfile> {
|
|
1363
|
+
return this.request<UserProfile>('GET', '/user/profile');
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
getUserCredits(): Promise<UserCredits> {
|
|
1367
|
+
return this.request<UserCredits>('GET', '/user/credits');
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// ─── Mail & Tracking Settings ─────────────────────────────────────────────────
|
|
1371
|
+
|
|
1372
|
+
listMailSettings(params?: {
|
|
1373
|
+
limit?: number;
|
|
1374
|
+
offset?: number;
|
|
1375
|
+
}): Promise<MailSettingsListResponse> {
|
|
1376
|
+
return this.request<MailSettingsListResponse>(
|
|
1377
|
+
'GET',
|
|
1378
|
+
'/mail_settings',
|
|
1379
|
+
undefined,
|
|
1380
|
+
{
|
|
1381
|
+
limit: params?.limit,
|
|
1382
|
+
offset: params?.offset,
|
|
1383
|
+
},
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
getMailSetting(name: MailSettingName): Promise<Record<string, unknown>> {
|
|
1388
|
+
return this.request<Record<string, unknown>>(
|
|
1389
|
+
'GET',
|
|
1390
|
+
`/mail_settings/${encodeURIComponent(name)}`,
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
updateMailSetting(
|
|
1395
|
+
name: MailSettingName,
|
|
1396
|
+
payload: Record<string, unknown>,
|
|
1397
|
+
): Promise<Record<string, unknown>> {
|
|
1398
|
+
return this.request<Record<string, unknown>>(
|
|
1399
|
+
'PATCH',
|
|
1400
|
+
`/mail_settings/${encodeURIComponent(name)}`,
|
|
1401
|
+
payload,
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
listTrackingSettings(): Promise<TrackingSettingsListResponse> {
|
|
1406
|
+
return this.request<TrackingSettingsListResponse>(
|
|
1407
|
+
'GET',
|
|
1408
|
+
'/tracking_settings',
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
getTrackingSetting(
|
|
1413
|
+
name: TrackingSettingName,
|
|
1414
|
+
): Promise<Record<string, unknown>> {
|
|
1415
|
+
return this.request<Record<string, unknown>>(
|
|
1416
|
+
'GET',
|
|
1417
|
+
`/tracking_settings/${encodeURIComponent(name)}`,
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
updateTrackingSetting(
|
|
1422
|
+
name: TrackingSettingName,
|
|
1423
|
+
payload: Record<string, unknown>,
|
|
1424
|
+
): Promise<Record<string, unknown>> {
|
|
1425
|
+
return this.request<Record<string, unknown>>(
|
|
1426
|
+
'PATCH',
|
|
1427
|
+
`/tracking_settings/${encodeURIComponent(name)}`,
|
|
1428
|
+
payload,
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// ─── Sender Identities & Domain Auth ──────────────────────────────────────────
|
|
1433
|
+
|
|
1434
|
+
getVerifiedSender(id: number): Promise<VerifiedSender> {
|
|
1435
|
+
return this.request<VerifiedSender>(
|
|
1436
|
+
'GET',
|
|
1437
|
+
`/verified_senders/${encodeURIComponent(String(id))}`,
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
createVerifiedSender(
|
|
1442
|
+
payload: CreateVerifiedSenderPayload,
|
|
1443
|
+
): Promise<VerifiedSender> {
|
|
1444
|
+
return this.request<VerifiedSender>('POST', '/verified_senders', payload);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
resendVerifiedSenderVerification(id: number): Promise<void> {
|
|
1448
|
+
return this.request<void>(
|
|
1449
|
+
'POST',
|
|
1450
|
+
`/verified_senders/resend/${encodeURIComponent(String(id))}`,
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
deleteVerifiedSender(id: number): Promise<void> {
|
|
1455
|
+
return this.request<void>(
|
|
1456
|
+
'DELETE',
|
|
1457
|
+
`/verified_senders/${encodeURIComponent(String(id))}`,
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
getAuthenticatedDomain(id: number): Promise<AuthenticatedDomain> {
|
|
1462
|
+
return this.request<AuthenticatedDomain>(
|
|
1463
|
+
'GET',
|
|
1464
|
+
`/whitelabel/domains/${encodeURIComponent(String(id))}`,
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
createAuthenticatedDomain(
|
|
1469
|
+
payload: CreateAuthenticatedDomainPayload,
|
|
1470
|
+
): Promise<AuthenticatedDomain> {
|
|
1471
|
+
return this.request<AuthenticatedDomain>(
|
|
1472
|
+
'POST',
|
|
1473
|
+
'/whitelabel/domains',
|
|
1474
|
+
payload,
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
validateAuthenticatedDomain(
|
|
1479
|
+
id: number,
|
|
1480
|
+
): Promise<Record<string, unknown>> {
|
|
1481
|
+
return this.request<Record<string, unknown>>(
|
|
1482
|
+
'POST',
|
|
1483
|
+
`/whitelabel/domains/${encodeURIComponent(String(id))}/validate`,
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
getBrandedLink(id: number): Promise<BrandedLink> {
|
|
1488
|
+
return this.request<BrandedLink>(
|
|
1489
|
+
'GET',
|
|
1490
|
+
`/whitelabel/links/${encodeURIComponent(String(id))}`,
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
validateBrandedLink(id: number): Promise<Record<string, unknown>> {
|
|
1495
|
+
return this.request<Record<string, unknown>>(
|
|
1496
|
+
'POST',
|
|
1497
|
+
`/whitelabel/links/${encodeURIComponent(String(id))}/validate`,
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
updateBrandedLink(
|
|
1502
|
+
id: number,
|
|
1503
|
+
payload: UpdateBrandedLinkPayload,
|
|
1504
|
+
): Promise<BrandedLink> {
|
|
1505
|
+
return this.request<BrandedLink>(
|
|
1506
|
+
'PATCH',
|
|
1507
|
+
`/whitelabel/links/${encodeURIComponent(String(id))}`,
|
|
1508
|
+
payload,
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// ─── Alerts ───────────────────────────────────────────────────────────────────
|
|
1513
|
+
|
|
1514
|
+
async listAlerts(): Promise<SendGridAlert[]> {
|
|
1515
|
+
const payload = await this.request<unknown>('GET', '/alerts');
|
|
1516
|
+
return coerceArray<SendGridAlert>(payload);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
getAlert(id: number): Promise<SendGridAlert> {
|
|
1520
|
+
return this.request<SendGridAlert>(
|
|
1521
|
+
'GET',
|
|
1522
|
+
`/alerts/${encodeURIComponent(String(id))}`,
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
createAlert(payload: CreateAlertPayload): Promise<SendGridAlert> {
|
|
1527
|
+
return this.request<SendGridAlert>('POST', '/alerts', payload);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
updateAlert(
|
|
1531
|
+
id: number,
|
|
1532
|
+
payload: UpdateAlertPayload,
|
|
1533
|
+
): Promise<SendGridAlert> {
|
|
1534
|
+
return this.request<SendGridAlert>(
|
|
1535
|
+
'PATCH',
|
|
1536
|
+
`/alerts/${encodeURIComponent(String(id))}`,
|
|
1537
|
+
payload,
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
deleteAlert(id: number): Promise<void> {
|
|
1542
|
+
return this.request<void>(
|
|
1543
|
+
'DELETE',
|
|
1544
|
+
`/alerts/${encodeURIComponent(String(id))}`,
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// ─── Inbound Parse ────────────────────────────────────────────────────────────
|
|
1549
|
+
|
|
1550
|
+
async listInboundParseSettings(): Promise<InboundParseSetting[]> {
|
|
1551
|
+
const payload = await this.request<unknown>(
|
|
1552
|
+
'GET',
|
|
1553
|
+
'/user/webhooks/parse/settings',
|
|
1554
|
+
);
|
|
1555
|
+
return coerceArray<InboundParseSetting>(payload);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
createInboundParseSetting(
|
|
1559
|
+
payload: CreateInboundParsePayload,
|
|
1560
|
+
): Promise<InboundParseSetting> {
|
|
1561
|
+
return this.request<InboundParseSetting>(
|
|
1562
|
+
'POST',
|
|
1563
|
+
'/user/webhooks/parse/settings',
|
|
1564
|
+
payload,
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
updateInboundParseSetting(
|
|
1569
|
+
hostname: string,
|
|
1570
|
+
payload: UpdateInboundParsePayload,
|
|
1571
|
+
): Promise<InboundParseSetting> {
|
|
1572
|
+
return this.request<InboundParseSetting>(
|
|
1573
|
+
'PATCH',
|
|
1574
|
+
`/user/webhooks/parse/settings/${encodeURIComponent(hostname)}`,
|
|
1575
|
+
payload,
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
deleteInboundParseSetting(hostname: string): Promise<void> {
|
|
1580
|
+
return this.request<void>(
|
|
1581
|
+
'DELETE',
|
|
1582
|
+
`/user/webhooks/parse/settings/${encodeURIComponent(hostname)}`,
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
}
|