groundmail 0.0.0-stage → 0.14.2
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 +48 -2
- package/dist/authjs.d.ts +72 -0
- package/dist/authjs.js +101 -0
- package/dist/better-auth.d.ts +122 -0
- package/dist/better-auth.js +148 -0
- package/dist/chunk-2ZKYDDVV.js +1397 -0
- package/dist/clerk.d.ts +64 -0
- package/dist/clerk.js +51 -0
- package/dist/client-B8xoWTgr.d.ts +961 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +20 -0
- package/dist/nodemailer.d.ts +1 -0
- package/dist/nodemailer.js +17 -0
- package/dist/payload.d.ts +87 -0
- package/dist/payload.js +114 -0
- package/package.json +99 -4
package/README.md
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
-
#
|
|
1
|
+
# groundmail
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official Node.js / TypeScript client for the [Groundmail](https://groundmail.dev) transactional email API.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install groundmail
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
export GROUNDMAIL_API_KEY=gm_your_key_here
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Groundmail } from 'groundmail'
|
|
15
|
+
|
|
16
|
+
const tx = new Groundmail(process.env.GROUNDMAIL_API_KEY!)
|
|
17
|
+
|
|
18
|
+
const email = await tx.emails.send({
|
|
19
|
+
from: 'ops@yourdomain.com',
|
|
20
|
+
to: 'user@example.com',
|
|
21
|
+
subject: 'Your receipt',
|
|
22
|
+
html: '<p>Thanks for your purchase.</p>',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
console.log(email.id, email.status)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Adapters
|
|
29
|
+
|
|
30
|
+
Optional entry points (install peer deps as needed):
|
|
31
|
+
|
|
32
|
+
| Import | Package / peer |
|
|
33
|
+
|--------|----------------|
|
|
34
|
+
| `groundmail/nodemailer` | [groundmail-nodemailer](https://www.npmjs.com/package/groundmail-nodemailer) + `nodemailer` |
|
|
35
|
+
| `groundmail/better-auth` | [groundmail-better-auth](https://www.npmjs.com/package/groundmail-better-auth) |
|
|
36
|
+
| `groundmail/authjs` | [groundmail-authjs](https://www.npmjs.com/package/groundmail-authjs) |
|
|
37
|
+
| `groundmail/clerk` | [groundmail-clerk](https://www.npmjs.com/package/groundmail-clerk) |
|
|
38
|
+
| `groundmail/payload` | [groundmail-payload](https://www.npmjs.com/package/groundmail-payload) |
|
|
39
|
+
|
|
40
|
+
## Docs
|
|
41
|
+
|
|
42
|
+
- Quickstart: https://docs.groundmail.dev/quickstart
|
|
43
|
+
- Node / Nodemailer: https://docs.groundmail.dev/frameworks/node
|
|
44
|
+
- API reference: https://docs.groundmail.dev/api/emails
|
|
45
|
+
- Product: https://groundmail.dev
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
MIT
|
package/dist/authjs.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { G as Groundmail } from './client-B8xoWTgr.js';
|
|
2
|
+
|
|
3
|
+
/** Subset of Auth.js theme used for the default magic-link email. */
|
|
4
|
+
type AuthjsTheme = {
|
|
5
|
+
brandColor?: string;
|
|
6
|
+
buttonText?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Minimal Auth.js email-provider config shape.
|
|
10
|
+
* Compatible with `next-auth` / `@auth/core` `EmailConfig` without requiring those packages at build time.
|
|
11
|
+
*/
|
|
12
|
+
type AuthjsEmailConfig = {
|
|
13
|
+
id: string;
|
|
14
|
+
type: 'email';
|
|
15
|
+
name: string;
|
|
16
|
+
from: string;
|
|
17
|
+
maxAge: number;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
sendVerificationRequest: (params: {
|
|
20
|
+
identifier: string;
|
|
21
|
+
url: string;
|
|
22
|
+
expires: Date;
|
|
23
|
+
provider: AuthjsEmailConfig;
|
|
24
|
+
token: string;
|
|
25
|
+
theme: AuthjsTheme;
|
|
26
|
+
request: Request;
|
|
27
|
+
}) => Promise<void>;
|
|
28
|
+
options: GroundmailAuthjsOptions;
|
|
29
|
+
};
|
|
30
|
+
type GroundmailAuthjsOptions = {
|
|
31
|
+
/** Verified sender, e.g. `Auth <noreply@yourdomain.com>`. Defaults to a placeholder that you must override for production. */
|
|
32
|
+
from?: string;
|
|
33
|
+
/** Groundmail API key (`gm_…`). Falls back to `AUTH_GROUNDMAIL_KEY` then `GROUNDMAIL_API_KEY`. */
|
|
34
|
+
apiKey?: string;
|
|
35
|
+
/** Override API base URL (tests / self-hosted). */
|
|
36
|
+
baseUrl?: string;
|
|
37
|
+
/** Injected client (tests). */
|
|
38
|
+
client?: Groundmail;
|
|
39
|
+
/** Token lifetime in seconds (Auth.js default is 24h). */
|
|
40
|
+
maxAge?: number;
|
|
41
|
+
/** Override the default HTML/text templates. */
|
|
42
|
+
sendVerificationRequest?: AuthjsEmailConfig['sendVerificationRequest'];
|
|
43
|
+
/** Provider id in Auth.js (default `groundmail`). */
|
|
44
|
+
id?: string;
|
|
45
|
+
/** Display name in Auth.js (default `Groundmail`). */
|
|
46
|
+
name?: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Auth.js / NextAuth email provider for Groundmail.
|
|
50
|
+
*
|
|
51
|
+
* Drop-in for magic-link sign-in. Requires a database adapter (Auth.js stores
|
|
52
|
+
* verification tokens).
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import NextAuth from 'next-auth'
|
|
57
|
+
* import Groundmail from 'groundmail-authjs'
|
|
58
|
+
*
|
|
59
|
+
* export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
60
|
+
* adapter: ...,
|
|
61
|
+
* providers: [
|
|
62
|
+
* Groundmail({
|
|
63
|
+
* from: 'noreply@yourdomain.com',
|
|
64
|
+
* // apiKey: process.env.GROUNDMAIL_API_KEY, // or AUTH_GROUNDMAIL_KEY
|
|
65
|
+
* }),
|
|
66
|
+
* ],
|
|
67
|
+
* })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function GroundmailAuthjs(config?: GroundmailAuthjsOptions): AuthjsEmailConfig;
|
|
71
|
+
|
|
72
|
+
export { type AuthjsEmailConfig, type AuthjsTheme, GroundmailAuthjs, type GroundmailAuthjsOptions, GroundmailAuthjs as default };
|
package/dist/authjs.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_API_BASE_URL,
|
|
3
|
+
Groundmail
|
|
4
|
+
} from "./chunk-2ZKYDDVV.js";
|
|
5
|
+
|
|
6
|
+
// src/authjs.ts
|
|
7
|
+
function escapeHtml(value) {
|
|
8
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
9
|
+
}
|
|
10
|
+
function signInHtml(params) {
|
|
11
|
+
const { url, host, theme } = params;
|
|
12
|
+
const escapedHost = escapeHtml(host.replace(/\./g, ".\u200B"));
|
|
13
|
+
const brandColor = theme.brandColor || "#0f766e";
|
|
14
|
+
const buttonText = theme.buttonText || "#fff";
|
|
15
|
+
const safeUrl = escapeHtml(url);
|
|
16
|
+
return `
|
|
17
|
+
<body style="background:#f8fafc;">
|
|
18
|
+
<table width="100%" border="0" cellspacing="20" cellpadding="0"
|
|
19
|
+
style="background:#ffffff; max-width:600px; margin:auto; border-radius:10px;">
|
|
20
|
+
<tr>
|
|
21
|
+
<td align="center"
|
|
22
|
+
style="padding:10px 0; font-size:22px; font-family:Helvetica,Arial,sans-serif; color:#334155;">
|
|
23
|
+
Sign in to <strong>${escapedHost}</strong>
|
|
24
|
+
</td>
|
|
25
|
+
</tr>
|
|
26
|
+
<tr>
|
|
27
|
+
<td align="center" style="padding:20px 0;">
|
|
28
|
+
<a href="${safeUrl}" target="_blank"
|
|
29
|
+
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; background:${brandColor};">
|
|
30
|
+
Sign in
|
|
31
|
+
</a>
|
|
32
|
+
</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr>
|
|
35
|
+
<td align="center"
|
|
36
|
+
style="padding:0 0 10px; font-size:16px; line-height:22px; font-family:Helvetica,Arial,sans-serif; color:#64748b;">
|
|
37
|
+
If you did not request this email you can safely ignore it.
|
|
38
|
+
</td>
|
|
39
|
+
</tr>
|
|
40
|
+
</table>
|
|
41
|
+
</body>
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
function signInText(params) {
|
|
45
|
+
return `Sign in to ${params.host}
|
|
46
|
+
${params.url}
|
|
47
|
+
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
function resolveApiKey(options) {
|
|
51
|
+
return options.apiKey ?? (typeof process !== "undefined" ? process.env.AUTH_GROUNDMAIL_KEY ?? process.env.GROUNDMAIL_API_KEY : void 0);
|
|
52
|
+
}
|
|
53
|
+
function GroundmailAuthjs(config = {}) {
|
|
54
|
+
const apiKey = resolveApiKey(config);
|
|
55
|
+
const from = (config.from ?? "Groundmail <noreply@mail.groundmail.dev>").trim();
|
|
56
|
+
if (!from) {
|
|
57
|
+
throw new Error("GroundmailAuthjs requires a non-empty `from` address");
|
|
58
|
+
}
|
|
59
|
+
const provider = {
|
|
60
|
+
id: config.id ?? "groundmail",
|
|
61
|
+
type: "email",
|
|
62
|
+
name: config.name ?? "Groundmail",
|
|
63
|
+
from,
|
|
64
|
+
maxAge: config.maxAge ?? 24 * 60 * 60,
|
|
65
|
+
apiKey,
|
|
66
|
+
options: config,
|
|
67
|
+
async sendVerificationRequest(params) {
|
|
68
|
+
if (config.sendVerificationRequest) {
|
|
69
|
+
await config.sendVerificationRequest(params);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const { identifier: to, provider: prov, url, theme } = params;
|
|
73
|
+
const client = config.client ?? (() => {
|
|
74
|
+
const key = prov.apiKey ?? resolveApiKey(config);
|
|
75
|
+
if (!key) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"Groundmail Auth.js provider requires `apiKey`, AUTH_GROUNDMAIL_KEY, or GROUNDMAIL_API_KEY"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return new Groundmail(key, {
|
|
81
|
+
baseUrl: config.baseUrl ?? DEFAULT_API_BASE_URL
|
|
82
|
+
});
|
|
83
|
+
})();
|
|
84
|
+
const { host } = new URL(url);
|
|
85
|
+
await client.emails.send({
|
|
86
|
+
from: prov.from,
|
|
87
|
+
to,
|
|
88
|
+
subject: `Sign in to ${host}`,
|
|
89
|
+
html: signInHtml({ url, host, theme }),
|
|
90
|
+
text: signInText({ url, host }),
|
|
91
|
+
tags: [{ name: "auth", value: "authjs-magic-link" }]
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
return provider;
|
|
96
|
+
}
|
|
97
|
+
var authjs_default = GroundmailAuthjs;
|
|
98
|
+
export {
|
|
99
|
+
GroundmailAuthjs,
|
|
100
|
+
authjs_default as default
|
|
101
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { S as SendEmailResult, G as Groundmail } from './client-B8xoWTgr.js';
|
|
2
|
+
|
|
3
|
+
type BetterAuthEmailUser = {
|
|
4
|
+
email: string;
|
|
5
|
+
name?: string | null;
|
|
6
|
+
id?: string;
|
|
7
|
+
};
|
|
8
|
+
type BetterAuthEmailContext = {
|
|
9
|
+
user: BetterAuthEmailUser;
|
|
10
|
+
url: string;
|
|
11
|
+
token?: string;
|
|
12
|
+
};
|
|
13
|
+
type BetterAuthMagicLinkContext = {
|
|
14
|
+
email: string;
|
|
15
|
+
url: string;
|
|
16
|
+
token?: string;
|
|
17
|
+
metadata?: unknown;
|
|
18
|
+
};
|
|
19
|
+
type BetterAuthEmailOtpType = 'sign-in' | 'email-verification' | 'forget-password' | string;
|
|
20
|
+
type BetterAuthEmailOtpContext = {
|
|
21
|
+
email: string;
|
|
22
|
+
otp: string;
|
|
23
|
+
type: BetterAuthEmailOtpType;
|
|
24
|
+
};
|
|
25
|
+
type BetterAuthInvitationContext = {
|
|
26
|
+
email: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
role?: string;
|
|
29
|
+
organization?: {
|
|
30
|
+
name?: string | null;
|
|
31
|
+
};
|
|
32
|
+
inviter?: {
|
|
33
|
+
user?: {
|
|
34
|
+
name?: string | null;
|
|
35
|
+
email?: string | null;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
invitation?: {
|
|
39
|
+
id?: string;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
type BetterAuthEmailOptions = {
|
|
43
|
+
/** Verified sender, e.g. `noreply@yourdomain.com`. */
|
|
44
|
+
from: string;
|
|
45
|
+
apiKey?: string;
|
|
46
|
+
baseUrl?: string;
|
|
47
|
+
/** Prebuilt client (tests / advanced wiring). */
|
|
48
|
+
client?: Groundmail;
|
|
49
|
+
/**
|
|
50
|
+
* When true (default), callbacks do not await the API call so Better Auth
|
|
51
|
+
* request timing is less coupled to delivery. Errors are logged.
|
|
52
|
+
*/
|
|
53
|
+
fireAndForget?: boolean;
|
|
54
|
+
/** Base URL used to build organization invite links when only an id is provided. */
|
|
55
|
+
appUrl?: string;
|
|
56
|
+
};
|
|
57
|
+
type BetterAuthEmailHelpers = {
|
|
58
|
+
/** Low-level send used by the Better Auth callbacks. */
|
|
59
|
+
sendAuthEmail: (params: {
|
|
60
|
+
to: string;
|
|
61
|
+
subject: string;
|
|
62
|
+
html: string;
|
|
63
|
+
text?: string;
|
|
64
|
+
tag?: string;
|
|
65
|
+
}) => Promise<SendEmailResult>;
|
|
66
|
+
/** Core: `emailVerification.sendVerificationEmail` */
|
|
67
|
+
sendVerificationEmail: (ctx: BetterAuthEmailContext, request?: unknown) => Promise<void>;
|
|
68
|
+
/** Core: `emailAndPassword.sendResetPassword` */
|
|
69
|
+
sendResetPassword: (ctx: BetterAuthEmailContext, request?: unknown) => Promise<void>;
|
|
70
|
+
/** Plugin: `magicLink({ sendMagicLink })` — see https://www.better-auth.com/docs/plugins/magic-link */
|
|
71
|
+
sendMagicLink: (ctx: BetterAuthMagicLinkContext, request?: unknown) => Promise<void>;
|
|
72
|
+
/** Plugin: `emailOTP({ sendVerificationOTP })` — see https://better-auth.com/docs/plugins/email-otp */
|
|
73
|
+
sendVerificationOTP: (ctx: BetterAuthEmailOtpContext, request?: unknown) => Promise<void>;
|
|
74
|
+
/** Plugin: `organization({ sendInvitationEmail })` */
|
|
75
|
+
sendInvitationEmail: (ctx: BetterAuthInvitationContext, request?: unknown) => Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Generic link email for custom flows.
|
|
78
|
+
* Pass your own subject + tag.
|
|
79
|
+
*/
|
|
80
|
+
sendLinkEmail: (ctx: BetterAuthEmailContext & {
|
|
81
|
+
subject: string;
|
|
82
|
+
tag?: string;
|
|
83
|
+
}, request?: unknown) => Promise<void>;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Groundmail helpers for Better Auth email callbacks.
|
|
87
|
+
*
|
|
88
|
+
* Better Auth’s [plugin catalog](https://better-auth.com/docs/plugins) covers
|
|
89
|
+
* Magic Link, Email OTP, Organization, etc. There is no third-party “email
|
|
90
|
+
* provider” plugin slot. Delivery is always a callback you supply on core
|
|
91
|
+
* config and on those plugins. This helper returns those callbacks.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { betterAuth } from 'better-auth'
|
|
96
|
+
* import { magicLink } from 'better-auth/plugins'
|
|
97
|
+
* import { emailOTP } from 'better-auth/plugins'
|
|
98
|
+
* import { createBetterAuthEmail } from 'groundmail-better-auth'
|
|
99
|
+
*
|
|
100
|
+
* const email = createBetterAuthEmail({
|
|
101
|
+
* from: 'noreply@yourdomain.com',
|
|
102
|
+
* apiKey: process.env.GROUNDMAIL_API_KEY!,
|
|
103
|
+
* })
|
|
104
|
+
*
|
|
105
|
+
* export const auth = betterAuth({
|
|
106
|
+
* emailVerification: {
|
|
107
|
+
* sendVerificationEmail: email.sendVerificationEmail,
|
|
108
|
+
* },
|
|
109
|
+
* emailAndPassword: {
|
|
110
|
+
* enabled: true,
|
|
111
|
+
* sendResetPassword: email.sendResetPassword,
|
|
112
|
+
* },
|
|
113
|
+
* plugins: [
|
|
114
|
+
* magicLink({ sendMagicLink: email.sendMagicLink }),
|
|
115
|
+
* emailOTP({ sendVerificationOTP: email.sendVerificationOTP }),
|
|
116
|
+
* ],
|
|
117
|
+
* })
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare function createBetterAuthEmail(options: BetterAuthEmailOptions): BetterAuthEmailHelpers;
|
|
121
|
+
|
|
122
|
+
export { type BetterAuthEmailContext, type BetterAuthEmailHelpers, type BetterAuthEmailOptions, type BetterAuthEmailOtpContext, type BetterAuthEmailOtpType, type BetterAuthEmailUser, type BetterAuthInvitationContext, type BetterAuthMagicLinkContext, createBetterAuthEmail };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_API_BASE_URL,
|
|
3
|
+
Groundmail
|
|
4
|
+
} from "./chunk-2ZKYDDVV.js";
|
|
5
|
+
|
|
6
|
+
// src/better-auth.ts
|
|
7
|
+
function linkHtml(label, url) {
|
|
8
|
+
const safe = escapeHtml(url);
|
|
9
|
+
return `<p>${escapeHtml(label)}: <a href="${safe}">${safe}</a></p>`;
|
|
10
|
+
}
|
|
11
|
+
function escapeHtml(value) {
|
|
12
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
13
|
+
}
|
|
14
|
+
function otpSubject(type) {
|
|
15
|
+
switch (type) {
|
|
16
|
+
case "sign-in":
|
|
17
|
+
return "Your sign-in code";
|
|
18
|
+
case "email-verification":
|
|
19
|
+
return "Your verification code";
|
|
20
|
+
case "forget-password":
|
|
21
|
+
return "Your password reset code";
|
|
22
|
+
default:
|
|
23
|
+
return "Your verification code";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function createBetterAuthEmail(options) {
|
|
27
|
+
const from = options.from.trim();
|
|
28
|
+
if (!from) {
|
|
29
|
+
throw new Error("createBetterAuthEmail requires a non-empty `from` address");
|
|
30
|
+
}
|
|
31
|
+
let client = options.client;
|
|
32
|
+
if (!client) {
|
|
33
|
+
const apiKey = options.apiKey ?? process.env.GROUNDMAIL_API_KEY;
|
|
34
|
+
if (!apiKey) {
|
|
35
|
+
throw new Error("createBetterAuthEmail requires `apiKey` or GROUNDMAIL_API_KEY");
|
|
36
|
+
}
|
|
37
|
+
client = new Groundmail(apiKey, {
|
|
38
|
+
baseUrl: options.baseUrl ?? DEFAULT_API_BASE_URL
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const fireAndForget = options.fireAndForget !== false;
|
|
42
|
+
const appUrl = options.appUrl?.replace(/\/$/, "");
|
|
43
|
+
const sendAuthEmail = async (params) => {
|
|
44
|
+
return client.emails.send({
|
|
45
|
+
from,
|
|
46
|
+
to: params.to,
|
|
47
|
+
subject: params.subject,
|
|
48
|
+
html: params.html,
|
|
49
|
+
text: params.text,
|
|
50
|
+
tags: params.tag ? [{ name: "auth", value: params.tag }] : [{ name: "auth", value: "transactional" }]
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const run = async (work) => {
|
|
54
|
+
if (!fireAndForget) {
|
|
55
|
+
await work();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
void work().catch((error) => {
|
|
59
|
+
console.error("[groundmail/better-auth] failed to send auth email", error);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const sendLinkEmail = async (ctx) => {
|
|
63
|
+
const to = ctx.user.email?.trim();
|
|
64
|
+
if (!to) throw new Error("Better Auth user is missing an email address");
|
|
65
|
+
await run(
|
|
66
|
+
() => sendAuthEmail({
|
|
67
|
+
to,
|
|
68
|
+
subject: ctx.subject,
|
|
69
|
+
html: linkHtml(ctx.subject, ctx.url),
|
|
70
|
+
text: `${ctx.subject}: ${ctx.url}`,
|
|
71
|
+
tag: ctx.tag
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
sendAuthEmail,
|
|
77
|
+
sendVerificationEmail: async (ctx) => {
|
|
78
|
+
await sendLinkEmail({
|
|
79
|
+
...ctx,
|
|
80
|
+
subject: "Verify your email",
|
|
81
|
+
tag: "verify"
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
sendResetPassword: async (ctx) => {
|
|
85
|
+
await sendLinkEmail({
|
|
86
|
+
...ctx,
|
|
87
|
+
subject: "Reset your password",
|
|
88
|
+
tag: "reset"
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
sendMagicLink: async (ctx) => {
|
|
92
|
+
const to = ctx.email?.trim();
|
|
93
|
+
if (!to) throw new Error("Magic link email is missing");
|
|
94
|
+
await run(
|
|
95
|
+
() => sendAuthEmail({
|
|
96
|
+
to,
|
|
97
|
+
subject: "Your sign-in link",
|
|
98
|
+
html: linkHtml("Sign in", ctx.url),
|
|
99
|
+
text: `Sign in: ${ctx.url}`,
|
|
100
|
+
tag: "magic-link"
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
sendVerificationOTP: async (ctx) => {
|
|
105
|
+
const to = ctx.email?.trim();
|
|
106
|
+
if (!to) throw new Error("OTP email is missing");
|
|
107
|
+
const subject = otpSubject(ctx.type);
|
|
108
|
+
const otp = String(ctx.otp ?? "").trim();
|
|
109
|
+
if (!otp) throw new Error("OTP value is missing");
|
|
110
|
+
await run(
|
|
111
|
+
() => sendAuthEmail({
|
|
112
|
+
to,
|
|
113
|
+
subject,
|
|
114
|
+
html: `<p>Your code is <strong>${escapeHtml(otp)}</strong></p>`,
|
|
115
|
+
text: `Your code is ${otp}`,
|
|
116
|
+
tag: `otp-${ctx.type}`
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
sendInvitationEmail: async (ctx) => {
|
|
121
|
+
const to = ctx.email?.trim();
|
|
122
|
+
if (!to) throw new Error("Invitation email is missing");
|
|
123
|
+
const inviteId = ctx.id ?? ctx.invitation?.id;
|
|
124
|
+
const inviteLink = inviteId && appUrl ? `${appUrl}/accept-invitation/${inviteId}` : inviteId ? `/accept-invitation/${inviteId}` : null;
|
|
125
|
+
if (!inviteLink) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"sendInvitationEmail needs an invitation id (and ideally `appUrl` on createBetterAuthEmail)"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const org = ctx.organization?.name?.trim() || "the organization";
|
|
131
|
+
const inviter = ctx.inviter?.user?.name?.trim() || ctx.inviter?.user?.email?.trim() || "A teammate";
|
|
132
|
+
const subject = `Join ${org}`;
|
|
133
|
+
await run(
|
|
134
|
+
() => sendAuthEmail({
|
|
135
|
+
to,
|
|
136
|
+
subject,
|
|
137
|
+
html: `<p>${escapeHtml(inviter)} invited you to join <strong>${escapeHtml(org)}</strong>.</p>${linkHtml("Accept invitation", inviteLink)}`,
|
|
138
|
+
text: `${inviter} invited you to join ${org}. Accept: ${inviteLink}`,
|
|
139
|
+
tag: "invite"
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
sendLinkEmail
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export {
|
|
147
|
+
createBetterAuthEmail
|
|
148
|
+
};
|