supersendtx 0.4.0 → 0.6.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/dist/index.d.ts CHANGED
@@ -8,10 +8,24 @@ type SendEmailParams = {
8
8
  subject: string;
9
9
  html?: string;
10
10
  text?: string;
11
- replyTo?: string;
12
- reply_to?: string;
11
+ replyTo?: string | string[];
12
+ reply_to?: string | string[];
13
13
  cc?: string | string[];
14
14
  bcc?: string | string[];
15
+ tags?: Array<{
16
+ name: string;
17
+ value: string;
18
+ }> | Record<string, string>;
19
+ headers?: Record<string, string>;
20
+ tag?: string;
21
+ attachments?: Array<{
22
+ name: string;
23
+ content_type: string;
24
+ data: string;
25
+ content_id?: string;
26
+ }>;
27
+ scheduledAt?: string | Date;
28
+ scheduled_at?: string | Date;
15
29
  /** Optional Idempotency-Key (24h TTL). */
16
30
  idempotencyKey?: string;
17
31
  };
@@ -19,14 +33,38 @@ type SendEmailResult = {
19
33
  id: string;
20
34
  status: string;
21
35
  };
36
+ type BatchSendEmailResult = {
37
+ data: Array<{
38
+ index: number;
39
+ id?: string;
40
+ status?: string;
41
+ error?: {
42
+ message: string;
43
+ code?: string;
44
+ details?: unknown;
45
+ status_code?: number;
46
+ };
47
+ }>;
48
+ };
49
+ type UpdateEmailParams = {
50
+ scheduledAt?: string | Date;
51
+ scheduled_at?: string | Date;
52
+ cancel?: boolean;
53
+ };
22
54
  type EmailListItem = {
23
55
  id: string;
24
56
  from: string;
25
57
  to: string[];
58
+ cc: string[];
59
+ bcc: string[];
60
+ reply_to: string[];
26
61
  subject: string;
27
62
  status: string;
28
63
  last_event: string | null;
29
64
  bounce_reason: string | null;
65
+ tags: unknown;
66
+ scheduled_at: string | null;
67
+ cancelled_at: string | null;
30
68
  created_at: string;
31
69
  sent_at: string | null;
32
70
  delivered_at: string | null;
@@ -71,7 +109,7 @@ type CreateApiKeyResult = {
71
109
  token: string;
72
110
  created_at: string;
73
111
  };
74
- type WebhookEventType = 'email.delivered' | 'email.bounced';
112
+ type WebhookEventType = 'email.sent' | 'email.delivered' | 'email.delivery_delayed' | 'email.bounced' | 'email.complained' | 'email.opened' | 'email.clicked' | 'email.failed' | 'email.suppressed' | 'email.scheduled';
75
113
  type WebhookEndpoint = {
76
114
  id: string;
77
115
  url: string;
@@ -84,10 +122,37 @@ type CreateWebhookParams = {
84
122
  url: string;
85
123
  events?: WebhookEventType[];
86
124
  };
125
+ type UpdateWebhookParams = {
126
+ url?: string;
127
+ events?: WebhookEventType[];
128
+ enabled?: boolean;
129
+ };
87
130
  type WebhookEndpointWithSecret = {
88
131
  webhook: WebhookEndpoint;
89
132
  secret: string;
90
133
  };
134
+ type Suppression = {
135
+ id: string;
136
+ email: string;
137
+ reason: string | null;
138
+ source: string;
139
+ created_at: string;
140
+ updated_at: string;
141
+ };
142
+ type ListSuppressionsParams = {
143
+ limit?: number;
144
+ cursor?: string;
145
+ email?: string;
146
+ };
147
+ type ListSuppressionsResult = {
148
+ data: Suppression[];
149
+ has_more: boolean;
150
+ next_cursor: string | null;
151
+ };
152
+ type CreateSuppressionParams = {
153
+ email: string;
154
+ reason?: string;
155
+ };
91
156
  type SuperSendTXOptions = {
92
157
  baseUrl?: string;
93
158
  fetch?: typeof fetch;
@@ -99,6 +164,8 @@ type Domain = {
99
164
  status: DomainStatus | string;
100
165
  created_at: string;
101
166
  verified_at: string | null;
167
+ open_tracking?: boolean;
168
+ click_tracking?: boolean;
102
169
  };
103
170
  type DnsRecord = {
104
171
  type: string;
@@ -197,6 +264,10 @@ declare class EmailsResource {
197
264
  email: EmailListItem;
198
265
  }>;
199
266
  send(params: SendEmailParams): Promise<SendEmailResult>;
267
+ batch(emails: SendEmailParams[]): Promise<BatchSendEmailResult>;
268
+ update(id: string, params: UpdateEmailParams): Promise<SendEmailResult>;
269
+ cancel(id: string): Promise<SendEmailResult>;
270
+ private serializeSendParams;
200
271
  }
201
272
 
202
273
  declare class KeysResource {
@@ -209,6 +280,19 @@ declare class KeysResource {
209
280
  }>;
210
281
  }
211
282
 
283
+ declare class SuppressionsResource {
284
+ private readonly client;
285
+ constructor(client: SuperSendTX);
286
+ list(params?: ListSuppressionsParams): Promise<ListSuppressionsResult>;
287
+ create(params: CreateSuppressionParams): Promise<{
288
+ suppression: Suppression;
289
+ }>;
290
+ /** Remove by suppression id or email address. */
291
+ remove(idOrEmail: string): Promise<{
292
+ ok: boolean;
293
+ }>;
294
+ }
295
+
212
296
  declare class WebhooksResource {
213
297
  private readonly client;
214
298
  constructor(client: SuperSendTX);
@@ -220,10 +304,18 @@ declare class WebhooksResource {
220
304
  has_more: boolean;
221
305
  next_cursor: string | null;
222
306
  }>;
307
+ get(id: string): Promise<{
308
+ webhook: WebhookEndpoint;
309
+ }>;
223
310
  create(params: CreateWebhookParams): Promise<WebhookEndpointWithSecret>;
311
+ update(id: string, params: UpdateWebhookParams): Promise<{
312
+ webhook: WebhookEndpoint;
313
+ }>;
224
314
  delete(id: string): Promise<{
225
315
  ok: boolean;
226
316
  }>;
317
+ /** Verify an incoming webhook signature (same as `verifyWebhookSignature`). */
318
+ verify(secret: string, payload: string, header: string | null | undefined, toleranceSec?: number): boolean;
227
319
  }
228
320
 
229
321
  declare const DEFAULT_API_BASE_URL = "https://api.supersendtx.com";
@@ -234,6 +326,7 @@ declare class SuperSendTX {
234
326
  readonly emails: EmailsResource;
235
327
  readonly webhooks: WebhooksResource;
236
328
  readonly apiKeys: KeysResource;
329
+ readonly suppressions: SuppressionsResource;
237
330
  constructor(apiKey: string, options?: SuperSendTXOptions);
238
331
  get baseUrl(): string;
239
332
  get fetchImpl(): typeof fetch;
@@ -252,6 +345,14 @@ declare class SuperSendTXError extends Error {
252
345
  static fromResponse(status: number, body: ApiErrorBody): SuperSendTXError;
253
346
  }
254
347
 
348
+ declare function signWebhookPayload(secret: string, payload: string, timestamp: number): string;
349
+ declare function buildSignatureHeader(secret: string, payload: string, timestamp?: number): string;
350
+ /**
351
+ * Verify a SuperSend TX webhook signature header (`SuperSendTX-Signature`).
352
+ * Expects HMAC-SHA256 over `${timestamp}.${rawBody}` with the endpoint secret.
353
+ */
354
+ declare function verifyWebhookSignature(secret: string, payload: string, header: string | null | undefined, toleranceSec?: number): boolean;
355
+
255
356
  /** Merge `include:host` into an existing SPF record (or create a minimal one). */
256
357
  declare function mergeSpfInclude(existing: string | null | undefined, includeHost: string): string;
257
358
 
@@ -282,4 +383,4 @@ declare function applyDnsRecords(input: {
282
383
  fetchImpl?: typeof fetch;
283
384
  }): Promise<ApplyDnsResult>;
284
385
 
285
- export { type ApiErrorCode, type ApiKey, type ApiKeyScope, type ApplyDnsParams, type ApplyDnsRecordResult$1 as ApplyDnsRecordResult, type ApplyDnsResult$1 as ApplyDnsResult, type CreateApiKeyParams, type CreateApiKeyResult, type CreateDomainParams, type CreateDomainResult, type CreateWebhookParams, DEFAULT_API_BASE_URL, type DnsProviderName$1 as DnsProviderName, type DnsRecord, type Domain, type DomainStatus, type EmailListItem, type ListApiKeysParams, type ListApiKeysResult, type ListEmailsParams, type ListEmailsResult, type SendEmailParams, type SendEmailResult, SuperSendTX, SuperSendTXError, type SuperSendTXOptions, type VerifyDomainResult, type WebhookEndpoint, type WebhookEndpointWithSecret, type WebhookEventType, applyDnsRecords, mergeSpfInclude };
386
+ export { type ApiErrorCode, type ApiKey, type ApiKeyScope, type ApplyDnsParams, type ApplyDnsRecordResult$1 as ApplyDnsRecordResult, type ApplyDnsResult$1 as ApplyDnsResult, type BatchSendEmailResult, type CreateApiKeyParams, type CreateApiKeyResult, type CreateDomainParams, type CreateDomainResult, type CreateSuppressionParams, type CreateWebhookParams, DEFAULT_API_BASE_URL, type DnsProviderName$1 as DnsProviderName, type DnsRecord, type Domain, type DomainStatus, type EmailListItem, type ListApiKeysParams, type ListApiKeysResult, type ListEmailsParams, type ListEmailsResult, type ListSuppressionsParams, type ListSuppressionsResult, type SendEmailParams, type SendEmailResult, SuperSendTX, SuperSendTXError, type SuperSendTXOptions, type Suppression, type UpdateEmailParams, type UpdateWebhookParams, type VerifyDomainResult, type WebhookEndpoint, type WebhookEndpointWithSecret, type WebhookEventType, applyDnsRecords, buildSignatureHeader, mergeSpfInclude, signWebhookPayload, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -427,12 +427,20 @@ var EmailsResource = class {
427
427
  to: params.to,
428
428
  subject: params.subject
429
429
  };
430
- if (params.html) payload.html = params.html;
431
- if (params.text) payload.text = params.text;
430
+ if (params.html !== void 0) payload.html = params.html;
431
+ if (params.text !== void 0) payload.text = params.text;
432
432
  const replyTo = params.replyTo ?? params.reply_to;
433
- if (replyTo) payload.reply_to = replyTo;
434
- if (params.cc) payload.cc = params.cc;
435
- if (params.bcc) payload.bcc = params.bcc;
433
+ if (replyTo !== void 0) payload.reply_to = replyTo;
434
+ if (params.cc !== void 0) payload.cc = params.cc;
435
+ if (params.bcc !== void 0) payload.bcc = params.bcc;
436
+ if (params.tags !== void 0) payload.tags = params.tags;
437
+ if (params.headers !== void 0) payload.headers = params.headers;
438
+ if (params.tag !== void 0) payload.tag = params.tag;
439
+ if (params.attachments !== void 0) payload.attachments = params.attachments;
440
+ const scheduledAt = params.scheduledAt ?? params.scheduled_at;
441
+ if (scheduledAt !== void 0) {
442
+ payload.scheduled_at = scheduledAt instanceof Date ? scheduledAt.toISOString() : scheduledAt;
443
+ }
436
444
  const headers = {};
437
445
  if (params.idempotencyKey) {
438
446
  headers["Idempotency-Key"] = params.idempotencyKey;
@@ -443,6 +451,49 @@ var EmailsResource = class {
443
451
  body: JSON.stringify(payload)
444
452
  });
445
453
  }
454
+ async batch(emails) {
455
+ return this.client.request("/emails/batch", {
456
+ method: "POST",
457
+ body: JSON.stringify({ emails: emails.map((email) => this.serializeSendParams(email)) })
458
+ });
459
+ }
460
+ async update(id, params) {
461
+ const payload = {};
462
+ if (params.cancel !== void 0) payload.cancel = params.cancel;
463
+ const scheduledAt = params.scheduledAt ?? params.scheduled_at;
464
+ if (scheduledAt !== void 0) {
465
+ payload.scheduled_at = scheduledAt instanceof Date ? scheduledAt.toISOString() : scheduledAt;
466
+ }
467
+ return this.client.request(`/emails/${encodeURIComponent(id)}`, {
468
+ method: "PATCH",
469
+ body: JSON.stringify(payload)
470
+ });
471
+ }
472
+ async cancel(id) {
473
+ return this.update(id, { cancel: true });
474
+ }
475
+ serializeSendParams(params) {
476
+ const payload = {
477
+ from: params.from,
478
+ to: params.to,
479
+ subject: params.subject
480
+ };
481
+ if (params.html !== void 0) payload.html = params.html;
482
+ if (params.text !== void 0) payload.text = params.text;
483
+ const replyTo = params.replyTo ?? params.reply_to;
484
+ if (replyTo !== void 0) payload.reply_to = replyTo;
485
+ if (params.cc !== void 0) payload.cc = params.cc;
486
+ if (params.bcc !== void 0) payload.bcc = params.bcc;
487
+ if (params.tags !== void 0) payload.tags = params.tags;
488
+ if (params.headers !== void 0) payload.headers = params.headers;
489
+ if (params.tag !== void 0) payload.tag = params.tag;
490
+ if (params.attachments !== void 0) payload.attachments = params.attachments;
491
+ const scheduledAt = params.scheduledAt ?? params.scheduled_at;
492
+ if (scheduledAt !== void 0) {
493
+ payload.scheduled_at = scheduledAt instanceof Date ? scheduledAt.toISOString() : scheduledAt;
494
+ }
495
+ return payload;
496
+ }
446
497
  };
447
498
 
448
499
  // src/keys.ts
@@ -475,7 +526,69 @@ var KeysResource = class {
475
526
  }
476
527
  };
477
528
 
478
- // src/webhooks.ts
529
+ // src/suppressions.ts
530
+ var SuppressionsResource = class {
531
+ constructor(client) {
532
+ this.client = client;
533
+ }
534
+ async list(params = {}) {
535
+ const search = new URLSearchParams();
536
+ if (params.limit != null) search.set("limit", String(params.limit));
537
+ if (params.cursor) search.set("cursor", params.cursor);
538
+ if (params.email) search.set("email", params.email);
539
+ const query = search.toString();
540
+ return this.client.request(`/suppressions${query ? `?${query}` : ""}`, { method: "GET" });
541
+ }
542
+ async create(params) {
543
+ return this.client.request("/suppressions", {
544
+ method: "POST",
545
+ body: JSON.stringify(params)
546
+ });
547
+ }
548
+ /** Remove by suppression id or email address. */
549
+ async remove(idOrEmail) {
550
+ if (idOrEmail.includes("@")) {
551
+ return this.client.request(`/suppressions?email=${encodeURIComponent(idOrEmail)}`, {
552
+ method: "DELETE"
553
+ });
554
+ }
555
+ return this.client.request(`/suppressions/${encodeURIComponent(idOrEmail)}`, {
556
+ method: "DELETE"
557
+ });
558
+ }
559
+ };
560
+
561
+ // src/webhooks/verify.ts
562
+ import { createHmac, timingSafeEqual } from "crypto";
563
+ function signWebhookPayload(secret, payload, timestamp) {
564
+ const signed = `${timestamp}.${payload}`;
565
+ return createHmac("sha256", secret).update(signed).digest("hex");
566
+ }
567
+ function buildSignatureHeader(secret, payload, timestamp = Math.floor(Date.now() / 1e3)) {
568
+ const signature = signWebhookPayload(secret, payload, timestamp);
569
+ return `t=${timestamp},v1=${signature}`;
570
+ }
571
+ function verifyWebhookSignature(secret, payload, header, toleranceSec = 300) {
572
+ if (!header) return false;
573
+ const parts = Object.fromEntries(
574
+ header.split(",").map((part) => {
575
+ const [key, value] = part.trim().split("=");
576
+ return [key, value];
577
+ })
578
+ );
579
+ const timestamp = Number(parts.t);
580
+ const signature = parts.v1;
581
+ if (!timestamp || !signature) return false;
582
+ if (Math.abs(Math.floor(Date.now() / 1e3) - timestamp) > toleranceSec) return false;
583
+ const expected = signWebhookPayload(secret, payload, timestamp);
584
+ try {
585
+ return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
586
+ } catch {
587
+ return false;
588
+ }
589
+ }
590
+
591
+ // src/webhooks/index.ts
479
592
  var WebhooksResource = class {
480
593
  constructor(client) {
481
594
  this.client = client;
@@ -487,15 +600,28 @@ var WebhooksResource = class {
487
600
  const query = search.toString();
488
601
  return this.client.request(`/webhooks${query ? `?${query}` : ""}`, { method: "GET" });
489
602
  }
603
+ async get(id) {
604
+ return this.client.request(`/webhooks/${encodeURIComponent(id)}`, { method: "GET" });
605
+ }
490
606
  async create(params) {
491
607
  return this.client.request("/webhooks", {
492
608
  method: "POST",
493
609
  body: JSON.stringify(params)
494
610
  });
495
611
  }
612
+ async update(id, params) {
613
+ return this.client.request(`/webhooks/${encodeURIComponent(id)}`, {
614
+ method: "PATCH",
615
+ body: JSON.stringify(params)
616
+ });
617
+ }
496
618
  async delete(id) {
497
619
  return this.client.request(`/webhooks/${id}`, { method: "DELETE" });
498
620
  }
621
+ /** Verify an incoming webhook signature (same as `verifyWebhookSignature`). */
622
+ verify(secret, payload, header, toleranceSec = 300) {
623
+ return verifyWebhookSignature(secret, payload, header, toleranceSec);
624
+ }
499
625
  };
500
626
 
501
627
  // src/client.ts
@@ -511,6 +637,7 @@ var SuperSendTX = class {
511
637
  this.emails = new EmailsResource(this);
512
638
  this.webhooks = new WebhooksResource(this);
513
639
  this.apiKeys = new KeysResource(this);
640
+ this.suppressions = new SuppressionsResource(this);
514
641
  }
515
642
  get baseUrl() {
516
643
  return (this.options.baseUrl || DEFAULT_API_BASE_URL).replace(/\/$/, "");
@@ -539,5 +666,8 @@ export {
539
666
  SuperSendTX,
540
667
  SuperSendTXError,
541
668
  applyDnsRecords,
542
- mergeSpfInclude
669
+ buildSignatureHeader,
670
+ mergeSpfInclude,
671
+ signWebhookPayload,
672
+ verifyWebhookSignature
543
673
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supersendtx",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "SuperSend TX transactional email API client",
5
5
  "license": "MIT",
6
6
  "type": "module",