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/README.md +40 -10
- package/dist/device.js +1 -1
- package/dist/index.d.ts +900 -151
- package/dist/index.js +425 -25
- package/package.json +14 -2
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
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`);
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
4
|
-
"description": "The official TypeScript SDK for
|
|
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
|
},
|