coss 0.3.0 → 0.4.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.js CHANGED
@@ -9,7 +9,7 @@ const API_VERSION = "2026-10-10";
9
9
  //#endregion
10
10
  //#region src/version.ts
11
11
  /** SDK version, sent in the User-Agent. A test keeps it equal to package.json's `version`. */
12
- const VERSION = "0.3.0";
12
+ const VERSION = "0.4.0";
13
13
  //#endregion
14
14
  //#region src/core/errors.ts
15
15
  const API_ERROR_TYPES = /* @__PURE__ */ new Set([
@@ -49,7 +49,7 @@ function errorFromResponse(status, statusText, body, headerRequestId) {
49
49
  if (!isRecord(envelope) || typeof envelope.message !== "string") return {
50
50
  type: "api_error",
51
51
  code: "unexpected_response",
52
- message: `The COSS API returned an unexpected HTTP ${status}${statusText ? ` (${statusText})` : ""} response.`,
52
+ message: `The coss.com API returned an unexpected HTTP ${status}${statusText ? ` (${statusText})` : ""} response.`,
53
53
  status,
54
54
  request_id: headerRequestId
55
55
  };
@@ -208,7 +208,7 @@ var HttpClient = class {
208
208
  };
209
209
  const timeoutMessage = `Request timed out after ${this.#timeout}ms.`;
210
210
  return {
211
- result: failed(timeoutSignal.aborted ? clientError("timeout_error", "request_timeout", timeoutMessage) : clientError("connection_error", "network_error", `Could not reach the COSS API at ${this.#baseUrl}: ${describe(cause)}`)),
211
+ result: failed(timeoutSignal.aborted ? clientError("timeout_error", "request_timeout", timeoutMessage) : clientError("connection_error", "network_error", `Could not reach the coss.com API at ${this.#baseUrl}: ${describe(cause)}`)),
212
212
  retry: true,
213
213
  retryAfterMs: null
214
214
  };
@@ -249,7 +249,7 @@ var HttpClient = class {
249
249
  result: failed({
250
250
  type: "api_error",
251
251
  code: "invalid_response",
252
- message: `The COSS API returned an HTTP ${response.status} response that is not valid JSON.`,
252
+ message: `The coss.com API returned an HTTP ${response.status} response that is not valid JSON.`,
253
253
  status: response.status,
254
254
  request_id: requestId
255
255
  }),
@@ -266,6 +266,24 @@ function failed(error) {
266
266
  };
267
267
  }
268
268
  //#endregion
269
+ //#region src/resources/api-key.ts
270
+ /** `coss.apiKey`: the API key the client was created with. */
271
+ var ApiKeyResource = class {
272
+ #http;
273
+ /** @internal Use `new Coss().apiKey`. */
274
+ constructor(http) {
275
+ this.#http = http;
276
+ }
277
+ /** Revokes the client's own API key, at once: every later request with it fails. */
278
+ revoke(options) {
279
+ return this.#http.request({
280
+ method: "DELETE",
281
+ path: "/v1/api_key",
282
+ signal: options?.signal
283
+ });
284
+ }
285
+ };
286
+ //#endregion
269
287
  //#region src/resources/list-query.ts
270
288
  /** One query parameter: lists are comma-joined (an empty one is left out), dates ISO 8601. */
271
289
  function joined(value) {
@@ -284,7 +302,7 @@ function listQuery(params) {
284
302
  //#region src/resources/audit-events.ts
285
303
  /**
286
304
  * `coss.auditEvents`: the organization's audit log, newest first: changes made by members, API
287
- * keys and COSS support. Organization-wide, whichever environment the key belongs to.
305
+ * keys and coss.com support. Organization-wide, whichever environment the key belongs to.
288
306
  */
289
307
  var AuditEventsResource = class {
290
308
  #http;
@@ -387,6 +405,59 @@ var BillingResource = class {
387
405
  }
388
406
  };
389
407
  //#endregion
408
+ //#region src/resources/dmarc.ts
409
+ const dmarcPath = (domainId) => `/v1/domains/${encodeURIComponent(domainId)}/dmarc`;
410
+ /** `coss.domains.dmarc.reports`: the aggregate reports received about a domain. */
411
+ var DmarcReportsResource = class {
412
+ #http;
413
+ /** @internal Use `new Coss().domains.dmarc.reports`. */
414
+ constructor(http) {
415
+ this.#http = http;
416
+ }
417
+ /** Lists the domain's reports, newest first, without their rows. */
418
+ list(domainId, params = {}, options) {
419
+ return this.#http.request({
420
+ method: "GET",
421
+ path: `${dmarcPath(domainId)}/reports`,
422
+ query: listQuery(params),
423
+ signal: options?.signal
424
+ });
425
+ }
426
+ /** Retrieves one report with its rows. */
427
+ get(domainId, reportId, options) {
428
+ return this.#http.request({
429
+ method: "GET",
430
+ path: `${dmarcPath(domainId)}/reports/${encodeURIComponent(reportId)}`,
431
+ signal: options?.signal
432
+ });
433
+ }
434
+ };
435
+ /**
436
+ * `coss.domains.dmarc`: a domain's DMARC record, the guided path from `p=none` to `p=reject`, and
437
+ * the aggregate reports mailbox providers send to its `report_address`.
438
+ */
439
+ var DmarcResource = class {
440
+ #http;
441
+ /** The domain's aggregate reports. */
442
+ reports;
443
+ /** @internal Use `new Coss().domains.dmarc`. */
444
+ constructor(http) {
445
+ this.#http = http;
446
+ this.reports = new DmarcReportsResource(http);
447
+ }
448
+ /**
449
+ * The domain's DMARC status: its published record, the record to publish next and whether
450
+ * the last 14 days of reports say that is safe (`ready`), and a summary of those reports.
451
+ */
452
+ get(domainId, options) {
453
+ return this.#http.request({
454
+ method: "GET",
455
+ path: dmarcPath(domainId),
456
+ signal: options?.signal
457
+ });
458
+ }
459
+ };
460
+ //#endregion
390
461
  //#region src/resources/receiving-domains.ts
391
462
  const receivingPath = (domainId) => `/v1/domains/${encodeURIComponent(domainId)}/receiving`;
392
463
  /**
@@ -471,11 +542,14 @@ var DomainsResource = class {
471
542
  tracking;
472
543
  /** Each domain's receiving hostname: start and stop receiving email at it. */
473
544
  receiving;
545
+ /** Each domain's DMARC status, guidance and aggregate reports. */
546
+ dmarc;
474
547
  /** @internal Use `new Coss().domains`. */
475
548
  constructor(http) {
476
549
  this.#http = http;
477
550
  this.tracking = new TrackingDomainsResource(http);
478
551
  this.receiving = new ReceivingDomainsResource(http);
552
+ this.dmarc = new DmarcResource(http);
479
553
  }
480
554
  /**
481
555
  * Adds a domain and returns the DNS records to publish (`data.records`). The records are
@@ -529,6 +603,230 @@ var DomainsResource = class {
529
603
  }
530
604
  };
531
605
  //#endregion
606
+ //#region src/react.ts
607
+ /**
608
+ * React Email: a send's `react` is rendered into `html` (and `text`, unless given) by
609
+ * `@react-email/render`, an optional peer dependency imported only when a send has `react`, so the
610
+ * SDK itself keeps zero dependencies.
611
+ */
612
+ let renderer;
613
+ /** Imports `@react-email/render` once; `null` when it isn't installed. */
614
+ function loadReactEmailRenderer() {
615
+ renderer ??= (async () => {
616
+ try {
617
+ return await import("@react-email/render");
618
+ } catch {
619
+ return null;
620
+ }
621
+ })();
622
+ return renderer;
623
+ }
624
+ /**
625
+ * Turns `react` into `html` and `text`; params without it pass through. Rendering mistakes come back
626
+ * as `invalid_request_error`s naming `param`, like the API's validation errors, never thrown.
627
+ */
628
+ async function renderReactEmail(params, param, load = loadReactEmailRenderer) {
629
+ const { react, ...body } = params;
630
+ if (react === void 0) return {
631
+ body,
632
+ error: null
633
+ };
634
+ if (body.template_id !== void 0) return failure("validation_error", "Pass either `react` or `template_id`, not both.", param);
635
+ if (body.html !== void 0) return failure("validation_error", "Pass either `react` or `html`, not both.", param);
636
+ const renderer = await load();
637
+ if (!renderer) return failure("react_renderer_missing", "Sending `react` needs the `@react-email/render` package: install it next to `coss`.", param);
638
+ try {
639
+ const html = await renderer.render(react);
640
+ if (body.text !== void 0) return {
641
+ body: {
642
+ ...body,
643
+ html
644
+ },
645
+ error: null
646
+ };
647
+ const text = renderer.toPlainText ? renderer.toPlainText(html) : await renderer.render(react, { plainText: true });
648
+ return {
649
+ body: {
650
+ ...body,
651
+ html,
652
+ text
653
+ },
654
+ error: null
655
+ };
656
+ } catch (cause) {
657
+ return failure("react_render_failed", `Rendering \`react\` failed: ${cause instanceof Error ? cause.message : String(cause)}`, param);
658
+ }
659
+ }
660
+ function failure(code, message, param) {
661
+ return {
662
+ body: null,
663
+ error: {
664
+ ...clientError("invalid_request_error", code, message),
665
+ param
666
+ }
667
+ };
668
+ }
669
+ //#endregion
670
+ //#region src/resources/dedicated-ips.ts
671
+ /**
672
+ * `coss.email.dedicatedIps`: the organization's own sending IPs (the Dedicated IPs add-on),
673
+ * managed and warmed up automatically.
674
+ */
675
+ var DedicatedIpsResource = class {
676
+ #http;
677
+ /** @internal Use `new Coss().email.dedicatedIps`. */
678
+ constructor(http) {
679
+ this.#http = http;
680
+ }
681
+ /** The pool's status and IPs (`not_enabled` without the add-on). */
682
+ get(options) {
683
+ return this.#http.request({
684
+ method: "GET",
685
+ path: "/v1/dedicated_ips",
686
+ signal: options?.signal
687
+ });
688
+ }
689
+ };
690
+ //#endregion
691
+ //#region src/resources/email-templates.ts
692
+ const templatePath = (template) => `/v1/email_templates/${encodeURIComponent(template)}`;
693
+ /** `coss.email.templates.versions`: a template's published versions. */
694
+ var EmailTemplateVersionsResource = class {
695
+ #http;
696
+ /** @internal Use `new Coss().email.templates.versions`. */
697
+ constructor(http) {
698
+ this.#http = http;
699
+ }
700
+ /** Published versions, newest first. */
701
+ list(template, params = {}, options) {
702
+ return this.#http.request({
703
+ method: "GET",
704
+ path: `${templatePath(template)}/versions`,
705
+ query: listQuery(params),
706
+ signal: options?.signal
707
+ });
708
+ }
709
+ get(template, version, options) {
710
+ return this.#http.request({
711
+ method: "GET",
712
+ path: `${templatePath(template)}/versions/${version}`,
713
+ signal: options?.signal
714
+ });
715
+ }
716
+ /**
717
+ * Replaces the draft with this version's content; publish the draft to send it again. Never
718
+ * automatically retried: every restore is a new draft revision, so a repeat could overwrite an
719
+ * edit saved in between.
720
+ */
721
+ restore(template, version, options) {
722
+ return this.#http.request({
723
+ method: "POST",
724
+ path: `${templatePath(template)}/versions/${version}/restore`,
725
+ retry: false,
726
+ signal: options?.signal
727
+ });
728
+ }
729
+ };
730
+ /**
731
+ * `coss.email.templates`: the environment's email templates. Templates are addressed by id or
732
+ * alias. Edit the draft, publish it, then send with `coss.email.send({ template_id, variables })`.
733
+ */
734
+ var EmailTemplatesResource = class {
735
+ #http;
736
+ /** Published versions. */
737
+ versions;
738
+ /** @internal Use `new Coss().email.templates`. */
739
+ constructor(http) {
740
+ this.#http = http;
741
+ this.versions = new EmailTemplateVersionsResource(http);
742
+ }
743
+ /**
744
+ * Creates a template and its draft. Never automatically retried: the API doesn't deduplicate it
745
+ * (a template with an `alias` can't be created twice, though).
746
+ */
747
+ create(params, options) {
748
+ return this.#http.request({
749
+ method: "POST",
750
+ path: "/v1/email_templates",
751
+ retry: false,
752
+ body: params,
753
+ idempotencyKey: options?.idempotencyKey,
754
+ signal: options?.signal
755
+ });
756
+ }
757
+ /** The environment's templates, newest first. */
758
+ list(params = {}, options) {
759
+ return this.#http.request({
760
+ method: "GET",
761
+ path: "/v1/email_templates",
762
+ query: listQuery(params),
763
+ signal: options?.signal
764
+ });
765
+ }
766
+ get(template, options) {
767
+ return this.#http.request({
768
+ method: "GET",
769
+ path: templatePath(template),
770
+ signal: options?.signal
771
+ });
772
+ }
773
+ /** Changes its name, alias or description. */
774
+ update(template, params, options) {
775
+ return this.#http.request({
776
+ method: "PATCH",
777
+ path: templatePath(template),
778
+ body: params,
779
+ signal: options?.signal
780
+ });
781
+ }
782
+ /** Deletes it with every version. Emails already sent keep their content. */
783
+ delete(template, options) {
784
+ return this.#http.request({
785
+ method: "DELETE",
786
+ path: templatePath(template),
787
+ signal: options?.signal
788
+ });
789
+ }
790
+ /** The draft, with `problems` that keep it from being published. */
791
+ getDraft(template, options) {
792
+ return this.#http.request({
793
+ method: "GET",
794
+ path: `${templatePath(template)}/draft`,
795
+ signal: options?.signal
796
+ });
797
+ }
798
+ /**
799
+ * Replaces the whole draft. Sends keep using the published version until you publish. Never
800
+ * automatically retried: every save is a new revision, so a repeat whose first attempt was saved
801
+ * would fail with `template_revision_mismatch` (or, without `revision`, overwrite an edit saved
802
+ * in between). Read the draft again before retrying.
803
+ */
804
+ updateDraft(template, params, options) {
805
+ return this.#http.request({
806
+ method: "PUT",
807
+ path: `${templatePath(template)}/draft`,
808
+ retry: false,
809
+ body: params,
810
+ signal: options?.signal
811
+ });
812
+ }
813
+ /**
814
+ * Publishes the draft as the next version, which sends use from then on. Automatically retried
815
+ * only with a `revision`: a draft without changes since the last publish returns that version,
816
+ * and one saved again since is refused. Without it, a retry could publish someone else's newer
817
+ * edits as yet another version.
818
+ */
819
+ publish(template, params = {}, options) {
820
+ return this.#http.request({
821
+ method: "POST",
822
+ path: `${templatePath(template)}/publish`,
823
+ retry: params.revision !== void 0,
824
+ body: params,
825
+ signal: options?.signal
826
+ });
827
+ }
828
+ };
829
+ //#endregion
532
830
  //#region src/resources/received-emails.ts
533
831
  const emailPath = (id) => `/v1/received_emails/${encodeURIComponent(id)}`;
534
832
  /**
@@ -711,8 +1009,8 @@ var SuppressionResource = class {
711
1009
  const categoryPath = (category) => `/v1/tracking_settings/categories/${encodeURIComponent(category)}`;
712
1010
  /**
713
1011
  * `coss.email.tracking`: which emails of the API key's environment have their opens and clicks
714
- * tracked. Emails keep the settings that applied when they were sent; a send's own `tracking`
715
- * option wins over them.
1012
+ * tracked and get one-click unsubscribe headers. Emails keep the settings that applied when they
1013
+ * were sent; a send's own `tracking` option wins over them.
716
1014
  */
717
1015
  var TrackingResource = class {
718
1016
  #http;
@@ -780,24 +1078,37 @@ var EmailResource = class {
780
1078
  tracking;
781
1079
  /** Emails the environment received. */
782
1080
  received;
1081
+ /** The environment's email templates. */
1082
+ templates;
1083
+ /** The organization's dedicated IPs. */
1084
+ dedicatedIps;
783
1085
  /** @internal Use `new Coss().email`. */
784
1086
  constructor(http) {
785
1087
  this.#http = http;
786
1088
  this.suppressions = new SuppressionResource(http);
787
1089
  this.tracking = new TrackingResource(http);
788
1090
  this.received = new ReceivedEmailsResource(http);
1091
+ this.templates = new EmailTemplatesResource(http);
1092
+ this.dedicatedIps = new DedicatedIpsResource(http);
789
1093
  }
790
1094
  /**
791
- * Sends an email. It is returned `queued` and handed to the delivery provider moments later:
1095
+ * Sends an email, with its own subject and body or from a published template (`template_id`
1096
+ * and `variables`). It is returned `queued` and handed to the delivery provider moments later:
792
1097
  * `get` it or listen for the `email.sent` / `email.failed` webhooks for the outcome. Safe to
793
1098
  * retry: the request always carries an `Idempotency-Key` (yours, or a generated one reused
794
- * across automatic retries), so the API never sends it twice.
1099
+ * across automatic retries), so the API never sends it twice. A `react` component is rendered
1100
+ * once, before the first attempt.
795
1101
  */
796
- send(params, options) {
1102
+ async send(params, options) {
1103
+ const { body, error } = await renderReactEmail(params, "react");
1104
+ if (error) return {
1105
+ data: null,
1106
+ error
1107
+ };
797
1108
  return this.#http.request({
798
1109
  method: "POST",
799
1110
  path: "/v1/emails",
800
- body: params,
1111
+ body,
801
1112
  idempotencyKey: options?.idempotencyKey,
802
1113
  signal: options?.signal
803
1114
  });
@@ -808,15 +1119,32 @@ var EmailResource = class {
808
1119
  * recipients count against the send quota together. Safe to retry: one `Idempotency-Key` covers
809
1120
  * the whole batch. Larger lists need several calls, each with its own key.
810
1121
  */
811
- sendBatch(emails, options) {
1122
+ async sendBatch(emails, options) {
1123
+ const rendered = await Promise.all(emails.map((email, index) => renderReactEmail(email, `emails.${index}.react`)));
1124
+ const bodies = [];
1125
+ for (const { body, error } of rendered) {
1126
+ if (error) return {
1127
+ data: null,
1128
+ error
1129
+ };
1130
+ bodies.push(body);
1131
+ }
812
1132
  return this.#http.request({
813
1133
  method: "POST",
814
1134
  path: "/v1/emails/batch",
815
- body: { emails },
1135
+ body: { emails: bodies },
816
1136
  idempotencyKey: options?.idempotencyKey,
817
1137
  signal: options?.signal
818
1138
  });
819
1139
  }
1140
+ /** Retrieves a batch sent by `sendBatch`, with its emails' current statuses in request order. */
1141
+ getBatch(id, options) {
1142
+ return this.#http.request({
1143
+ method: "GET",
1144
+ path: `/v1/emails/batch/${encodeURIComponent(id)}`,
1145
+ signal: options?.signal
1146
+ });
1147
+ }
820
1148
  /** Retrieves an email sent from the same environment as the API key. */
821
1149
  get(id, options) {
822
1150
  return this.#http.request({
@@ -880,6 +1208,24 @@ var EmailResource = class {
880
1208
  }
881
1209
  };
882
1210
  //#endregion
1211
+ //#region src/resources/environment.ts
1212
+ /** `coss.environment`: which environment, project and organization the API key belongs to. */
1213
+ var EnvironmentResource = class {
1214
+ #http;
1215
+ /** @internal Use `new Coss().environment`. */
1216
+ constructor(http) {
1217
+ this.#http = http;
1218
+ }
1219
+ /** The API key's environment, its project and organization, and the key itself. */
1220
+ get(options) {
1221
+ return this.#http.request({
1222
+ method: "GET",
1223
+ path: "/v1/environment",
1224
+ signal: options?.signal
1225
+ });
1226
+ }
1227
+ };
1228
+ //#endregion
883
1229
  //#region src/resources/hooks.ts
884
1230
  const segment = encodeURIComponent;
885
1231
  const applicationPath = (application) => `/v1/hooks/applications/${segment(application)}`;
@@ -1093,7 +1439,7 @@ var HookEndpointsResource = class {
1093
1439
  });
1094
1440
  }
1095
1441
  };
1096
- /** `coss.hooks.messages`: send a message once; COSS delivers it to every subscribed endpoint. */
1442
+ /** `coss.hooks.messages`: send a message once; coss.com delivers it to every subscribed endpoint. */
1097
1443
  var HookMessagesResource = class {
1098
1444
  #http;
1099
1445
  /** @internal Use `new Coss().hooks.messages`. */
@@ -1196,7 +1542,7 @@ var HookPortalResource = class {
1196
1542
  };
1197
1543
  /**
1198
1544
  * `coss.hooks`: webhooks as a service. Send a message to one of your applications once
1199
- * (`coss.hooks.messages.send`); COSS delivers it to every endpoint its users registered, signed
1545
+ * (`coss.hooks.messages.send`); coss.com delivers it to every endpoint its users registered, signed
1200
1546
  * (Standard Webhooks), with retries, and gives them a portal to manage it all.
1201
1547
  */
1202
1548
  var HooksResource = class {
@@ -1292,7 +1638,7 @@ var LogDrainsResource = class {
1292
1638
  }
1293
1639
  /**
1294
1640
  * Changes the URL, description, sources or `enabled` flag. The signing secret stays the same.
1295
- * Disabling drops the lines still waiting for the drain; enabling it again (also after COSS
1641
+ * Disabling drops the lines still waiting for the drain; enabling it again (also after coss.com
1296
1642
  * disabled it for failing) sends lines logged from then on.
1297
1643
  */
1298
1644
  update(id, params, options) {
@@ -1348,7 +1694,7 @@ var LogsResource = class {
1348
1694
  //#endregion
1349
1695
  //#region src/resources/reputation.ts
1350
1696
  /**
1351
- * `coss.reputation`: the organization's standing with the COSS reputation guard, which suspends
1697
+ * `coss.reputation`: the organization's standing with the coss.com reputation guard, which suspends
1352
1698
  * live sending automatically when bounce or complaint rates cross its thresholds.
1353
1699
  */
1354
1700
  var ReputationResource = class {
@@ -1585,7 +1931,7 @@ var RiskLimitBucketsResource = class {
1585
1931
  });
1586
1932
  }
1587
1933
  };
1588
- /** `coss.risk.users`: what COSS remembers about your users. */
1934
+ /** `coss.risk.users`: what coss.com remembers about your users. */
1589
1935
  var RiskUsersResource = class {
1590
1936
  #http;
1591
1937
  /** @internal Use `new Coss().risk.users`. */
@@ -1593,7 +1939,7 @@ var RiskUsersResource = class {
1593
1939
  this.#http = http;
1594
1940
  }
1595
1941
  /**
1596
- * Call when you delete a user: COSS deletes what identifies them (their mailbox, fraud reports,
1942
+ * Call when you delete a user: coss.com deletes what identifies them (their mailbox, fraud reports,
1597
1943
  * and the user id, email, IP and user agent on their assessments). Safe to repeat.
1598
1944
  */
1599
1945
  forget(userId, options) {
@@ -1689,7 +2035,7 @@ var RiskResource = class {
1689
2035
  }
1690
2036
  /**
1691
2037
  * Checks an IP address on its own: Tor, cloud, hosting and VPN networks, ASN and country, and
1692
- * VPN, proxy and relay exits when COSS's privacy data is available.
2038
+ * VPN, proxy and relay exits when coss.com's privacy data is available.
1693
2039
  */
1694
2040
  checkIp(ip, options) {
1695
2041
  return this.#http.request({
@@ -1877,6 +2223,51 @@ var WebhookEndpointsResource = class {
1877
2223
  }
1878
2224
  };
1879
2225
  //#endregion
2226
+ //#region src/resources/webhook-listeners.ts
2227
+ /**
2228
+ * `coss.webhookListeners`: what `coss listen` uses to forward an environment's events to a local
2229
+ * server. A listener receives events created after it opens, while it is polled; it is closed by
2230
+ * `delete` or after 10 minutes without a poll.
2231
+ */
2232
+ var WebhookListenersResource = class {
2233
+ #http;
2234
+ /** @internal Use `new Coss().webhookListeners`. */
2235
+ constructor(http) {
2236
+ this.#http = http;
2237
+ }
2238
+ /** Opens a listener. Never retried automatically: a lost answer leaves one to expire. */
2239
+ create(params = {}, options) {
2240
+ return this.#http.request({
2241
+ method: "POST",
2242
+ path: "/v1/webhook_listeners",
2243
+ retry: false,
2244
+ body: params,
2245
+ signal: options?.signal
2246
+ });
2247
+ }
2248
+ /**
2249
+ * Acknowledges earlier deliveries and waits (up to `wait_ms`) for more. Never retried
2250
+ * automatically: call it in a loop, and keep unacknowledged acks for the next call after an error.
2251
+ */
2252
+ poll(id, params = {}, options) {
2253
+ return this.#http.request({
2254
+ method: "POST",
2255
+ path: `/v1/webhook_listeners/${encodeURIComponent(id)}/poll`,
2256
+ retry: false,
2257
+ body: params,
2258
+ signal: options?.signal
2259
+ });
2260
+ }
2261
+ /** Closes a listener: it receives nothing more. */
2262
+ delete(id, options) {
2263
+ return this.#http.request({
2264
+ method: "DELETE",
2265
+ path: `/v1/webhook_listeners/${encodeURIComponent(id)}`,
2266
+ signal: options?.signal
2267
+ });
2268
+ }
2269
+ };
2270
+ //#endregion
1880
2271
  //#region src/client.ts
1881
2272
  const DEFAULT_BASE_URL = "https://api.coss.com";
1882
2273
  const DEFAULT_TIMEOUT_MS = 3e4;
@@ -1897,7 +2288,7 @@ function timeoutMs(value) {
1897
2288
  return value !== void 0 && Number.isFinite(value) && value > 0 ? Math.min(Math.ceil(value), MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
1898
2289
  }
1899
2290
  /**
1900
- * The COSS client. One API key covers every product.
2291
+ * The coss.com client. One API key covers every product.
1901
2292
  *
1902
2293
  * ```ts
1903
2294
  * const coss = new Coss({ apiKey: "coss_test_…" });
@@ -1906,17 +2297,23 @@ function timeoutMs(value) {
1906
2297
  */
1907
2298
  var Coss = class {
1908
2299
  email;
2300
+ /** The API key's environment, project and organization (`coss.environment.get()`). */
2301
+ environment;
2302
+ /** The client's own API key (`coss.apiKey.revoke()`). */
2303
+ apiKey;
1909
2304
  /** Sending domains of the organization (`coss.domains.create`, `.verify`, …). */
1910
2305
  domains;
1911
2306
  /** Webhook endpoints of the API key's environment. */
1912
2307
  webhookEndpoints;
2308
+ /** Temporary listeners `coss listen` pulls the environment's events from. */
2309
+ webhookListeners;
1913
2310
  /** The organization's sending reputation (`coss.reputation.get()`). */
1914
2311
  reputation;
1915
2312
  /** The environment's log: API requests, email events, webhook attempts (`coss.logs.list()`). */
1916
2313
  logs;
1917
2314
  /** HTTPS endpoints the environment's log is streamed to (`coss.logDrains.create()`). */
1918
2315
  logDrains;
1919
- /** The organization's audit log: changes by members, API keys and COSS support (`coss.auditEvents.list()`). */
2316
+ /** The organization's audit log: changes by members, API keys and coss.com support (`coss.auditEvents.list()`). */
1920
2317
  auditEvents;
1921
2318
  /** Hooks, webhooks as a service: `coss.hooks.messages.send()` delivers to your users' endpoints. */
1922
2319
  hooks;
@@ -1929,9 +2326,9 @@ var Coss = class {
1929
2326
  * `dangerouslyAllowBrowser`. Request failures are returned as `{ data: null, error }`.
1930
2327
  */
1931
2328
  constructor(options = {}) {
1932
- if (typeof globalThis.document !== "undefined" && !options.dangerouslyAllowBrowser) throw new Error("Refusing to create a COSS client in a browser-like environment: COSS API keys are secret, and any key sent to the browser can be read by anyone using the page. Call COSS from your server instead, or pass `dangerouslyAllowBrowser: true` if you understand the risk.");
2329
+ if (typeof globalThis.document !== "undefined" && !options.dangerouslyAllowBrowser) throw new Error("Refusing to create a coss.com client in a browser-like environment: coss.com API keys are secret, and any key sent to the browser can be read by anyone using the page. Call coss.com from your server instead, or pass `dangerouslyAllowBrowser: true` if you understand the risk.");
1933
2330
  const apiKey = options.apiKey?.trim() || readEnv("COSS_API_KEY");
1934
- if (!apiKey) throw new Error("Missing COSS API key. Pass `apiKey` to `new Coss({ apiKey })` or set the COSS_API_KEY environment variable.");
2331
+ if (!apiKey) throw new Error("Missing coss.com API key. Pass `apiKey` to `new Coss({ apiKey })` or set the COSS_API_KEY environment variable.");
1935
2332
  const http = new HttpClient({
1936
2333
  apiKey,
1937
2334
  baseUrl: options.baseUrl?.trim() || readEnv("COSS_BASE_URL") || DEFAULT_BASE_URL,
@@ -1941,8 +2338,11 @@ var Coss = class {
1941
2338
  apiVersion: options.apiVersion?.trim() || "2026-10-10"
1942
2339
  });
1943
2340
  this.email = new EmailResource(http);
2341
+ this.environment = new EnvironmentResource(http);
2342
+ this.apiKey = new ApiKeyResource(http);
1944
2343
  this.domains = new DomainsResource(http);
1945
2344
  this.webhookEndpoints = new WebhookEndpointsResource(http);
2345
+ this.webhookListeners = new WebhookListenersResource(http);
1946
2346
  this.reputation = new ReputationResource(http);
1947
2347
  this.logs = new LogsResource(http);
1948
2348
  this.logDrains = new LogDrainsResource(http);
@@ -2016,7 +2416,7 @@ async function verifySignature(payload, headers, secret, options) {
2016
2416
  * const event = await verifyWebhook(await request.text(), request.headers, process.env.COSS_WEBHOOK_SECRET);
2017
2417
  * ```
2018
2418
  *
2019
- * The same check verifies messages your own users receive from COSS Hooks (`coss.hooks`): pass
2419
+ * The same check verifies messages your own users receive from coss.com Hooks (`coss.hooks`): pass
2020
2420
  * the endpoint's secret and name your payload type, e.g. `verifyWebhook<InvoicePaid>(…)`.
2021
2421
  */
2022
2422
  async function verifyWebhook(payload, headers, secret, options = {}) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "coss",
3
- "version": "0.3.0",
4
- "description": "The official TypeScript SDK for COSS: email, SMS, video, scheduling and more behind one API key.",
3
+ "version": "0.4.0",
4
+ "description": "The official TypeScript SDK for coss.com: email, SMS, video, scheduling and more behind one API key.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://coss.com/docs",
7
7
  "repository": {
@@ -31,10 +31,22 @@
31
31
  "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
32
32
  "test": "bun test"
33
33
  },
34
+ "peerDependencies": {
35
+ "@react-email/render": "^1.0.0 || ^2.0.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "@react-email/render": {
39
+ "optional": true
40
+ }
41
+ },
34
42
  "devDependencies": {
35
43
  "@arethetypeswrong/core": "0.18.5",
44
+ "@react-email/render": "2.1.0",
36
45
  "@types/bun": "1.4.2",
46
+ "@types/react": "19.3.0",
37
47
  "publint": "0.3.25",
48
+ "react": "19.3.0",
49
+ "react-dom": "19.3.0",
38
50
  "tsdown": "0.23.0",
39
51
  "typescript": "7.0.2"
40
52
  },