coss 0.3.0 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # coss
2
2
 
3
- The official TypeScript SDK for COSS: one API key for email, and soon SMS, video, scheduling and more.
3
+ The official TypeScript SDK for coss.com: one API key for email, and soon SMS, video, scheduling and more.
4
4
 
5
5
  ```sh
6
6
  npm install coss
@@ -10,7 +10,7 @@ Published from [cosscom/coss](https://github.com/cosscom/coss) by CI, with
10
10
  [provenance](https://docs.npmjs.com/generating-provenance-statements). Versions 0.0.1–0.0.5 of the npm package
11
11
  are an earlier, unrelated package by a previous owner (deprecated).
12
12
 
13
- - Zero dependencies, ESM-only, fully typed.
13
+ - Zero dependencies (React Email support is an optional peer), ESM-only, fully typed.
14
14
  - `coss.email`, `coss.domains`, `coss.webhookEndpoints` and `coss.reputation` behind one API key.
15
15
  - Every method returns `{ data, error }`. API and network failures are returned, never thrown.
16
16
  - Retries transient failures automatically, and sends are idempotent.
@@ -25,7 +25,7 @@ const coss = new Coss({ apiKey: process.env.COSS_API_KEY });
25
25
  const { data, error } = await coss.email.send({
26
26
  from: "Acme <onboarding@example.com>",
27
27
  to: "delivered@example.com",
28
- subject: "Hello from COSS",
28
+ subject: "Hello from coss.com",
29
29
  html: "<p>It works!</p>",
30
30
  });
31
31
 
@@ -73,7 +73,7 @@ The constructor throws in only two cases, both programmer errors:
73
73
  - It runs where a `document` global exists (a browser, or a jsdom or happy-dom test environment)
74
74
  without `dangerouslyAllowBrowser: true`.
75
75
 
76
- API keys are secrets, so call COSS from your server.
76
+ API keys are secrets, so call coss.com from your server.
77
77
 
78
78
  Each method also takes per-request options:
79
79
 
@@ -99,6 +99,35 @@ import { API_VERSION } from "coss";
99
99
  await coss.webhookEndpoints.update("whep_01k…", { api_version: API_VERSION });
100
100
  ```
101
101
 
102
+ ## React Email
103
+
104
+ Pass a [React Email](https://react.email) component as `react` instead of `html`. The SDK renders it
105
+ with `@react-email/render`, an optional peer dependency you install yourself (with `react` and
106
+ `react-dom`, which your components need anyway):
107
+
108
+ ```sh
109
+ npm install @react-email/render
110
+ ```
111
+
112
+ ```tsx
113
+ import { WelcomeEmail } from "./emails/welcome";
114
+
115
+ await coss.email.send({
116
+ from: "Acme <hello@acme.com>",
117
+ to: "ada@example.com",
118
+ subject: "Welcome to Acme",
119
+ react: <WelcomeEmail name="Ada" />,
120
+ });
121
+ ```
122
+
123
+ The component becomes `html`, and `text` too unless you pass your own. Rendering happens once, before
124
+ the first attempt, so retries resend the same body. `sendBatch` renders each email's `react` the same way.
125
+ Nothing throws: `react` together with `html` returns `validation_error`, a missing
126
+ `@react-email/render` returns `react_renderer_missing`, and a component that throws returns
127
+ `react_render_failed`, all `invalid_request_error`s with `param` naming the email (`react`,
128
+ `emails.3.react`) and no request made. The SDK itself still has no dependencies: the renderer is imported
129
+ only by a send that has `react`.
130
+
102
131
  ## Attachments
103
132
 
104
133
  Attach up to 20 files (25 MB combined) as base64. The content type defaults from the extension.
@@ -120,7 +149,7 @@ await coss.email.send({
120
149
  ```
121
150
 
122
151
  Emails only return attachment metadata (`id`, `filename`, `content_type`, `content_id`, `size`).
123
- COSS discards the content once the email is sent. Executables and other file types the delivery
152
+ coss.com discards the content once the email is sent. Executables and other file types the delivery
124
153
  provider refuses (`.exe`, `.js`, `.bat`, …) are rejected with a `validation_error`.
125
154
 
126
155
  ## Domains
@@ -148,7 +177,8 @@ await coss.domains.delete(domain!.id);
148
177
  rate limited: a second call within a few seconds answers `429` (the SDK waits out `Retry-After` and
149
178
  retries). Verification gives up after 72 hours and the domain becomes `failed`; calling `verify`
150
179
  again then starts over with fresh DKIM records, which have to be published too. The `dmarc` record
151
- is recommended rather than required (`required: false`).
180
+ is recommended rather than required (`required: false`). `bimi` reports whether mailbox providers can show
181
+ your brand logo (your own BIMI record and VMC or CMC certificate, with DMARC at enforcement).
152
182
 
153
183
  ## Webhook endpoints
154
184
 
@@ -241,7 +271,7 @@ Imported entries have the reason `import`. The dashboard imports and exports the
241
271
  ### Open and click tracking
242
272
 
243
273
  Tracking is off unless the environment turns it on in the dashboard (a default, plus rules per
244
- category) or a send asks for it. COSS then adds an invisible image to the HTML body and sends its
274
+ category) or a send asks for it. coss.com then adds an invisible image to the HTML body and sends its
245
275
  links through a redirect. Links with a `data-coss-no-track` attribute are left as they are.
246
276
 
247
277
  ```ts
@@ -264,9 +294,9 @@ one. Mail clients that preload images and link scanners can inflate the numbers.
264
294
 
265
295
  ## Reputation
266
296
 
267
- COSS watches your live bounce and complaint rates. When one gets too high your organization is
297
+ coss.com watches your live bounce and complaint rates. When one gets too high your organization is
268
298
  warned, and past the suspension threshold live sending is suspended automatically (sends fail with
269
- `permission_error` / `account_suspended`; sandbox keys keep working) until COSS support reinstates it.
299
+ `permission_error` / `account_suspended`; sandbox keys keep working) until coss.com support reinstates it.
270
300
 
271
301
  ```ts
272
302
  const { data } = await coss.reputation.get();
@@ -325,7 +355,7 @@ interface CossError {
325
355
  | `not_found_error` | 404 | No such resource in the key's environment |
326
356
  | `idempotency_error` | 409 / 422 | `request_in_progress` (retried automatically), or `idempotency_key_reused` with a different body |
327
357
  | `rate_limit_error` | 429 | Too many requests with the key (`rate_limited`), the send quota is used up (`quota_exceeded`), or a domain was checked moments ago (`domain_recently_checked`) |
328
- | `api_error` | 5xx, other | Server error. Also used for responses that are not a COSS error envelope (`unexpected_response`) |
358
+ | `api_error` | 5xx, other | Server error. Also used for responses that are not a coss.com error envelope (`unexpected_response`) |
329
359
  | `connection_error` | — | Network failure (`network_error`), or you aborted (`aborted`) |
330
360
  | `timeout_error` | — | An attempt exceeded `timeout` (`request_timeout`) |
331
361
 
@@ -361,6 +391,29 @@ To make a send safe across process restarts too, pass your own key, derived from
361
391
  cancel a call. Aborting stops the request in flight and any pending retry, and returns
362
392
  `{ type: "connection_error", code: "aborted" }`.
363
393
 
394
+ ## Integrations
395
+
396
+ To send from libraries you already use, import the matching entry. They need no other package
397
+ than the library itself, and they throw a `CossSendError` when coss.com refuses an email.
398
+
399
+ ```ts
400
+ // Nodemailer
401
+ import nodemailer from "nodemailer";
402
+ import { cossTransport } from "coss/nodemailer";
403
+ const transporter = nodemailer.createTransport(cossTransport());
404
+
405
+ // Better Auth: verification, password reset, magic link, one-time code and invitation emails
406
+ import { cossEmail } from "coss/better-auth";
407
+ const email = cossEmail({ from: "Acme <auth@acme.com>", appName: "Acme" });
408
+ // betterAuth({ emailVerification: { sendVerificationEmail: email.sendVerificationEmail }, … })
409
+
410
+ // Auth.js (NextAuth.js v5): the email provider
411
+ import Coss from "coss/authjs";
412
+ // NextAuth({ adapter, providers: [Coss({ from: "Acme <auth@acme.com>" })] })
413
+ ```
414
+
415
+ Guides: [coss.com/docs/integrations](https://coss.com/docs/integrations).
416
+
364
417
  ## Runtime support
365
418
 
366
419
  The SDK works on Node.js ≥ 22, Bun, Deno and Cloudflare Workers. It only needs the standard `fetch`,
@@ -0,0 +1,64 @@
1
+ import { n as IntegrationClientOptions, t as CossSendError } from "./integration-p3vtLNgq.js";
2
+ //#region src/authjs.d.ts
3
+ /** What Auth.js passes to `sendVerificationRequest` (the fields the provider reads). */
4
+ export interface CossVerificationRequestParams {
5
+ /** The address to send the link to. */
6
+ identifier: string;
7
+ url: string;
8
+ token: string;
9
+ expires: Date;
10
+ provider: {
11
+ apiKey?: string;
12
+ from?: string;
13
+ };
14
+ theme: {
15
+ brandColor?: string;
16
+ buttonText?: string;
17
+ };
18
+ }
19
+ /** An email's content, as `render` returns it. */
20
+ export interface CossVerificationEmail {
21
+ subject: string;
22
+ html: string;
23
+ text: string;
24
+ }
25
+ export interface CossProviderConfig extends IntegrationClientOptions {
26
+ /** The sender, e.g. `"Acme <auth@acme.com>"`, on a domain verified in coss.com (live keys). */
27
+ from?: string;
28
+ /** How long a link is valid, in seconds. Default 24 hours. */
29
+ maxAge?: number;
30
+ /** Replace the default email (Auth.js's layout). */
31
+ render?: (params: CossVerificationRequestParams & {
32
+ host: string;
33
+ }) => CossVerificationEmail;
34
+ /** Defaults to `"account"`. */
35
+ category?: string;
36
+ /** Provider id, default `"coss"` (also names the env variable: `AUTH_COSS_KEY`). */
37
+ id?: string;
38
+ /** Shown on Auth.js's sign-in page. Default `"Email"`. */
39
+ name?: string;
40
+ }
41
+ /** The provider object Auth.js reads. */
42
+ export interface CossEmailProvider {
43
+ id: string;
44
+ type: "email";
45
+ name: string;
46
+ from?: string;
47
+ maxAge: number;
48
+ apiKey?: string;
49
+ sendVerificationRequest: (params: CossVerificationRequestParams) => Promise<void>;
50
+ /** What Auth.js merges over the provider (a type literal: Auth.js wants a `Record`). */
51
+ options: {
52
+ id?: string;
53
+ name?: string;
54
+ from?: string;
55
+ maxAge?: number;
56
+ };
57
+ }
58
+ /**
59
+ * An Auth.js email provider. Links go out with `category: "account"` and open and click tracking
60
+ * off: click tracking would route the secret link through coss.com's redirect.
61
+ */
62
+ export declare function CossProvider(config?: CossProviderConfig): CossEmailProvider;
63
+ //#endregion
64
+ export { CossProvider as default, CossSendError };
package/dist/authjs.js ADDED
@@ -0,0 +1,97 @@
1
+ import { c as CossSendError, l as lazyClient, o as sha256Hex, r as escapeHtml } from "./mime-C8bhhjvD.js";
2
+ //#region src/authjs.ts
3
+ /**
4
+ * An Auth.js email provider. Links go out with `category: "account"` and open and click tracking
5
+ * off: click tracking would route the secret link through coss.com's redirect.
6
+ */
7
+ function CossProvider(config = {}) {
8
+ const clients = /* @__PURE__ */ new Map();
9
+ const clientFor = (apiKey) => {
10
+ const key = apiKey ?? config.apiKey ?? "";
11
+ let client = clients.get(key);
12
+ if (!client) {
13
+ client = lazyClient("authjs", config, () => apiKey ?? config.apiKey);
14
+ clients.set(key, client);
15
+ }
16
+ return client();
17
+ };
18
+ return {
19
+ id: config.id ?? "coss",
20
+ type: "email",
21
+ name: config.name ?? "Email",
22
+ from: config.from,
23
+ maxAge: config.maxAge ?? 86400,
24
+ apiKey: config.apiKey,
25
+ async sendVerificationRequest(params) {
26
+ const from = params.provider.from ?? config.from;
27
+ if (!from) throw new Error("coss/authjs: set `from`, e.g. Coss({ from: \"Acme <auth@acme.com>\" }).");
28
+ const host = new URL(params.url).host;
29
+ const email = (config.render ?? defaultEmail)({
30
+ ...params,
31
+ host
32
+ });
33
+ const result = await clientFor(params.provider.apiKey).email.send({
34
+ from,
35
+ to: params.identifier,
36
+ subject: email.subject,
37
+ html: email.html,
38
+ text: email.text,
39
+ category: config.category ?? "account",
40
+ tracking: {
41
+ opens: false,
42
+ clicks: false
43
+ }
44
+ }, { idempotencyKey: `authjs_${await sha256Hex(params.token)}` });
45
+ if (result.error) throw new CossSendError(result.error);
46
+ },
47
+ options: {
48
+ id: config.id,
49
+ name: config.name,
50
+ from: config.from,
51
+ maxAge: config.maxAge
52
+ }
53
+ };
54
+ }
55
+ /** Auth.js's own sign-in email, with the host's dots broken so clients don't link them. */
56
+ function defaultEmail({ url, host, theme }) {
57
+ const escapedHost = escapeHtml(host).replace(/\./g, "&#8203;.");
58
+ const brandColor = escapeHtml(theme.brandColor || "#346df1");
59
+ const buttonText = escapeHtml(theme.buttonText || "#fff");
60
+ const html = `<body style="background: #f9f9f9;">
61
+ <table width="100%" border="0" cellspacing="20" cellpadding="0"
62
+ style="background: #fff; max-width: 600px; margin: auto; border-radius: 10px;">
63
+ <tr>
64
+ <td align="center"
65
+ style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: #444;">
66
+ Sign in to <strong>${escapedHost}</strong>
67
+ </td>
68
+ </tr>
69
+ <tr>
70
+ <td align="center" style="padding: 20px 0;">
71
+ <table border="0" cellspacing="0" cellpadding="0">
72
+ <tr>
73
+ <td align="center" style="border-radius: 5px;" bgcolor="${brandColor}"><a href="${escapeHtml(url)}"
74
+ target="_blank"
75
+ style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${brandColor}; display: inline-block; font-weight: bold;">Sign
76
+ in</a></td>
77
+ </tr>
78
+ </table>
79
+ </td>
80
+ </tr>
81
+ <tr>
82
+ <td align="center"
83
+ style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: #444;">
84
+ If you did not request this email you can safely ignore it.
85
+ </td>
86
+ </tr>
87
+ </table>
88
+ </body>
89
+ `;
90
+ return {
91
+ subject: `Sign in to ${host}`,
92
+ html,
93
+ text: `Sign in to ${host}\n${url}\n\n`
94
+ };
95
+ }
96
+ //#endregion
97
+ export { CossProvider, CossProvider as default, CossSendError };
@@ -0,0 +1,128 @@
1
+ import { Ci as Email, Wi as SendEmailParams } from "./client-avnHVMry.js";
2
+ import { n as IntegrationClientOptions, t as CossSendError } from "./integration-p3vtLNgq.js";
3
+ //#region src/better-auth.d.ts
4
+ /** An email's content: what a template returns. */
5
+ export interface EmailContent {
6
+ subject: string;
7
+ html: string;
8
+ text: string;
9
+ }
10
+ /** The user Better Auth passes to its callbacks (only the fields the templates read). */
11
+ export interface BetterAuthUser {
12
+ email: string;
13
+ name?: string | null;
14
+ }
15
+ export interface VerificationEmailData {
16
+ user: BetterAuthUser;
17
+ url: string;
18
+ token: string;
19
+ }
20
+ export interface ResetPasswordData {
21
+ user: BetterAuthUser;
22
+ url: string;
23
+ token: string;
24
+ }
25
+ export interface ChangeEmailConfirmationData {
26
+ user: BetterAuthUser;
27
+ newEmail: string;
28
+ url: string;
29
+ token: string;
30
+ }
31
+ export interface DeleteAccountVerificationData {
32
+ user: BetterAuthUser;
33
+ url: string;
34
+ token: string;
35
+ }
36
+ export interface MagicLinkData {
37
+ email: string;
38
+ url: string;
39
+ token: string;
40
+ }
41
+ export interface VerificationOtpData {
42
+ email: string;
43
+ otp: string;
44
+ type: "sign-in" | "email-verification" | "forget-password" | "change-email";
45
+ }
46
+ export interface InvitationData {
47
+ id: string;
48
+ email: string;
49
+ role: string;
50
+ organization: {
51
+ name: string;
52
+ };
53
+ inviter: {
54
+ user: BetterAuthUser;
55
+ };
56
+ }
57
+ /** Template data: what Better Auth passed, plus your `appName`. */
58
+ type WithApp<T> = T & {
59
+ appName: string | undefined;
60
+ };
61
+ /** One template per email; each returns the subject and both bodies. */
62
+ export interface CossEmailTemplates {
63
+ verifyEmail: (data: WithApp<VerificationEmailData>) => EmailContent;
64
+ resetPassword: (data: WithApp<ResetPasswordData>) => EmailContent;
65
+ changeEmailConfirmation: (data: WithApp<ChangeEmailConfirmationData>) => EmailContent;
66
+ deleteAccount: (data: WithApp<DeleteAccountVerificationData>) => EmailContent;
67
+ magicLink: (data: WithApp<MagicLinkData>) => EmailContent;
68
+ verificationOtp: (data: WithApp<VerificationOtpData>) => EmailContent;
69
+ invitation: (data: WithApp<InvitationData & {
70
+ url: string;
71
+ }>) => EmailContent;
72
+ }
73
+ export interface CossEmailOptions extends IntegrationClientOptions {
74
+ /** The sender, e.g. `"Acme <auth@acme.com>"`, on a domain verified in coss.com (live keys). */
75
+ from: string;
76
+ /** Your product's name, used by the default templates ("Sign in to Acme"). */
77
+ appName?: string;
78
+ /** Replace any of the default templates. */
79
+ templates?: Partial<CossEmailTemplates>;
80
+ /**
81
+ * The page where an invitee accepts an organization invitation, e.g.
82
+ * `({ id }) => \`https://acme.com/accept-invitation/${id}\``. Better Auth passes no URL to
83
+ * `sendInvitationEmail`, so it needs this.
84
+ */
85
+ invitationUrl?: (data: InvitationData) => string;
86
+ /** Defaults to `"account"`, so suppressions scoped to other categories never block sign-in mail. */
87
+ category?: string;
88
+ }
89
+ /**
90
+ * Parameters of `send`: `from`, `category` and tracking default to the `cossEmail` options. Like
91
+ * `coss.email.send`, a send has its own content or names a template.
92
+ */
93
+ export type CossEmailSendParams = SendEmailParams extends (infer Params) ? Params extends SendEmailParams ? Omit<Params, "from"> & {
94
+ from?: string;
95
+ } : never : never;
96
+ /** The callbacks `cossEmail` returns; pass each to the matching Better Auth option. */
97
+ export interface CossEmail {
98
+ /** `emailVerification.sendVerificationEmail` */
99
+ sendVerificationEmail: (data: VerificationEmailData, request?: unknown) => Promise<void>;
100
+ /** `emailAndPassword.sendResetPassword` */
101
+ sendResetPassword: (data: ResetPasswordData, request?: unknown) => Promise<void>;
102
+ /** `user.changeEmail.sendChangeEmailConfirmation` (sent to the current address) */
103
+ sendChangeEmailConfirmation: (data: ChangeEmailConfirmationData, request?: unknown) => Promise<void>;
104
+ /** `user.deleteUser.sendDeleteAccountVerification` */
105
+ sendDeleteAccountVerification: (data: DeleteAccountVerificationData, request?: unknown) => Promise<void>;
106
+ /** `magicLink({ sendMagicLink })` */
107
+ sendMagicLink: (data: MagicLinkData, ctx?: unknown) => Promise<void>;
108
+ /** `emailOTP({ sendVerificationOTP })` */
109
+ sendVerificationOTP: (data: VerificationOtpData, ctx?: unknown) => Promise<void>;
110
+ /** `organization({ sendInvitationEmail })`; needs the `invitationUrl` option. */
111
+ sendInvitationEmail: (data: InvitationData, request?: unknown) => Promise<void>;
112
+ /** Sends any other email with the same defaults. Throws `CossSendError` on failure. */
113
+ send: (params: CossEmailSendParams, options?: {
114
+ idempotencyKey?: string;
115
+ }) => Promise<Email>;
116
+ }
117
+ /**
118
+ * Better Auth email callbacks that send through coss.com. Each throws `CossSendError` when coss.com
119
+ * refuses the email.
120
+ *
121
+ * Auth emails go out with `category: "account"` and open and click tracking off: click tracking
122
+ * would route the secret links through coss.com's redirect.
123
+ */
124
+ export declare function cossEmail(options: CossEmailOptions): CossEmail;
125
+ /** The default templates: plain, unbranded, with a text version of each. */
126
+ export declare const DEFAULT_TEMPLATES: CossEmailTemplates;
127
+ //#endregion
128
+ export { CossSendError };