@cms0/transactional 0.0.1
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/.env.example +3 -0
- package/README.md +136 -0
- package/emails/reset-password.tsx +107 -0
- package/emails/team-invite.tsx +109 -0
- package/package.json +40 -0
- package/src/client.ts +59 -0
- package/src/index.ts +63 -0
- package/src/templates.tsx +15 -0
- package/src/types.ts +54 -0
- package/tsconfig.json +11 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# @cms0/transactional
|
|
2
|
+
|
|
3
|
+
Transactional email package using [Plunk](https://useplunk.com) and [React Email](https://react.email).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
This package is part of the workspace. Install dependencies from the root:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Set your Plunk API key as an environment variable:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
PLUNK_API_KEY=sk_your_api_key_here
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Import and use the email sending functions:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import {
|
|
27
|
+
sendTeamInvite,
|
|
28
|
+
sendResetPassword,
|
|
29
|
+
} from '@cms0/transactional';
|
|
30
|
+
|
|
31
|
+
// Send a team invitation
|
|
32
|
+
await sendTeamInvite('user@example.com', {
|
|
33
|
+
teamName: 'Engineering Team',
|
|
34
|
+
inviterName: 'John Doe',
|
|
35
|
+
inviteUrl: 'https://app.example.com/accept-invite?token=xyz',
|
|
36
|
+
recipientEmail: 'user@example.com',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Send a password reset email
|
|
40
|
+
await sendResetPassword('user@example.com', {
|
|
41
|
+
resetUrl: 'https://app.example.com/reset-password?token=xyz',
|
|
42
|
+
userName: 'John', // optional
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Custom Options
|
|
47
|
+
|
|
48
|
+
All send functions accept an optional third parameter for custom options:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
await sendTeamInvite(
|
|
52
|
+
'user@example.com',
|
|
53
|
+
{
|
|
54
|
+
teamName: 'Engineering Team',
|
|
55
|
+
inviterName: 'John Doe',
|
|
56
|
+
inviteUrl: 'https://app.example.com/accept-invite?token=xyz',
|
|
57
|
+
recipientEmail: 'user@example.com',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
from: { name: 'Support Team', email: 'support@example.com' },
|
|
61
|
+
replyTo: 'support@example.com',
|
|
62
|
+
headers: {
|
|
63
|
+
'X-Custom-Header': 'value',
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Development
|
|
70
|
+
|
|
71
|
+
### Preview Emails
|
|
72
|
+
|
|
73
|
+
Start the React Email development server to preview all email templates:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
cd packages/transactional
|
|
77
|
+
pnpm dev
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then open [http://localhost:3000](http://localhost:3000) to preview your emails.
|
|
81
|
+
|
|
82
|
+
### Build
|
|
83
|
+
|
|
84
|
+
Compile TypeScript to JavaScript:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pnpm build
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Email Templates
|
|
91
|
+
|
|
92
|
+
This package includes the following email templates:
|
|
93
|
+
|
|
94
|
+
1. **team-invite** - Invite users to join a team
|
|
95
|
+
2. **reset-password** - Password reset instructions
|
|
96
|
+
|
|
97
|
+
All templates are built using React Email components and are located in the `emails/` directory.
|
|
98
|
+
|
|
99
|
+
## API Reference
|
|
100
|
+
|
|
101
|
+
### `sendTeamInvite(to, props, options?)`
|
|
102
|
+
|
|
103
|
+
Send a team invitation email.
|
|
104
|
+
|
|
105
|
+
**Parameters:**
|
|
106
|
+
- `to` (string): Recipient email address
|
|
107
|
+
- `props` (TeamInviteProps):
|
|
108
|
+
- `teamName` (string): Name of the team
|
|
109
|
+
- `inviterName` (string): Name of the person sending the invite
|
|
110
|
+
- `inviteUrl` (string): URL to accept the invitation
|
|
111
|
+
- `recipientEmail` (string): Email address of the recipient
|
|
112
|
+
- `options` (SendEmailOptions, optional): Custom email options
|
|
113
|
+
|
|
114
|
+
### `sendResetPassword(to, props, options?)`
|
|
115
|
+
|
|
116
|
+
Send a password reset email.
|
|
117
|
+
|
|
118
|
+
**Parameters:**
|
|
119
|
+
- `to` (string): Recipient email address
|
|
120
|
+
- `props` (ResetPasswordProps):
|
|
121
|
+
- `resetUrl` (string): URL to reset password
|
|
122
|
+
- `userName` (string, optional): Name of the user
|
|
123
|
+
- `options` (SendEmailOptions, optional): Custom email options
|
|
124
|
+
|
|
125
|
+
## Advanced Usage
|
|
126
|
+
|
|
127
|
+
### Custom Plunk Client
|
|
128
|
+
|
|
129
|
+
You can create and set a custom Plunk client:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { PlunkClient, setDefaultClient } from '@cms0/transactional';
|
|
133
|
+
|
|
134
|
+
const customClient = new PlunkClient('sk_custom_api_key');
|
|
135
|
+
setDefaultClient(customClient);
|
|
136
|
+
```
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Html,
|
|
4
|
+
Head,
|
|
5
|
+
Body,
|
|
6
|
+
Container,
|
|
7
|
+
Section,
|
|
8
|
+
Text,
|
|
9
|
+
Button,
|
|
10
|
+
Hr,
|
|
11
|
+
} from "@react-email/components";
|
|
12
|
+
|
|
13
|
+
interface ResetPasswordEmailProps {
|
|
14
|
+
resetUrl: string;
|
|
15
|
+
userName?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function ResetPasswordEmail({
|
|
19
|
+
resetUrl = "https://example.com/reset-password",
|
|
20
|
+
userName = "there",
|
|
21
|
+
}: ResetPasswordEmailProps) {
|
|
22
|
+
return (
|
|
23
|
+
<Html lang="en">
|
|
24
|
+
<Head />
|
|
25
|
+
<Body style={main}>
|
|
26
|
+
<Container style={container}>
|
|
27
|
+
<Section style={content}>
|
|
28
|
+
<Text style={heading}>Reset your password</Text>
|
|
29
|
+
<Text style={paragraph}>Hi {userName},</Text>
|
|
30
|
+
<Text style={paragraph}>
|
|
31
|
+
We received a request to reset your password. Click the button below to create a new
|
|
32
|
+
password:
|
|
33
|
+
</Text>
|
|
34
|
+
<Button href={resetUrl} style={button}>
|
|
35
|
+
Reset Password
|
|
36
|
+
</Button>
|
|
37
|
+
<Hr style={hr} />
|
|
38
|
+
<Text style={paragraph}>
|
|
39
|
+
This link will expire in 1 hour for security reasons.
|
|
40
|
+
</Text>
|
|
41
|
+
<Text style={footer}>
|
|
42
|
+
If you didn't request a password reset, you can safely ignore this email. Your
|
|
43
|
+
password will not be changed.
|
|
44
|
+
</Text>
|
|
45
|
+
</Section>
|
|
46
|
+
</Container>
|
|
47
|
+
</Body>
|
|
48
|
+
</Html>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const main = {
|
|
53
|
+
backgroundColor: "#f6f9fc",
|
|
54
|
+
fontFamily:
|
|
55
|
+
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const container = {
|
|
59
|
+
backgroundColor: "#ffffff",
|
|
60
|
+
margin: "0 auto",
|
|
61
|
+
padding: "20px 0 48px",
|
|
62
|
+
marginBottom: "64px",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const content = {
|
|
66
|
+
padding: "0 48px",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const heading = {
|
|
70
|
+
fontSize: "32px",
|
|
71
|
+
lineHeight: "1.3",
|
|
72
|
+
fontWeight: "700",
|
|
73
|
+
color: "#484848",
|
|
74
|
+
padding: "17px 0 0",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const paragraph = {
|
|
78
|
+
margin: "0 0 15px",
|
|
79
|
+
fontSize: "15px",
|
|
80
|
+
lineHeight: "1.4",
|
|
81
|
+
color: "#3c4149",
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const button = {
|
|
85
|
+
backgroundColor: "#000000",
|
|
86
|
+
borderRadius: "5px",
|
|
87
|
+
color: "#fff",
|
|
88
|
+
fontSize: "16px",
|
|
89
|
+
fontWeight: "bold",
|
|
90
|
+
textDecoration: "none",
|
|
91
|
+
textAlign: "center" as const,
|
|
92
|
+
display: "block",
|
|
93
|
+
width: "100%",
|
|
94
|
+
padding: "12px",
|
|
95
|
+
margin: "20px 0",
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const hr = {
|
|
99
|
+
borderColor: "#dfe1e4",
|
|
100
|
+
margin: "42px 0 26px",
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const footer = {
|
|
104
|
+
color: "#8898aa",
|
|
105
|
+
fontSize: "12px",
|
|
106
|
+
lineHeight: "16px",
|
|
107
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Html,
|
|
4
|
+
Head,
|
|
5
|
+
Body,
|
|
6
|
+
Container,
|
|
7
|
+
Section,
|
|
8
|
+
Text,
|
|
9
|
+
Button,
|
|
10
|
+
Hr,
|
|
11
|
+
} from "@react-email/components";
|
|
12
|
+
|
|
13
|
+
interface TeamInviteEmailProps {
|
|
14
|
+
teamName: string;
|
|
15
|
+
inviterName: string;
|
|
16
|
+
inviteUrl: string;
|
|
17
|
+
recipientEmail: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function TeamInviteEmail({
|
|
21
|
+
teamName = "Your Team",
|
|
22
|
+
inviterName = "A team member",
|
|
23
|
+
inviteUrl = "https://example.com/accept-invite",
|
|
24
|
+
recipientEmail = "user@example.com",
|
|
25
|
+
}: TeamInviteEmailProps) {
|
|
26
|
+
return (
|
|
27
|
+
<Html lang="en">
|
|
28
|
+
<Head />
|
|
29
|
+
<Body style={main}>
|
|
30
|
+
<Container style={container}>
|
|
31
|
+
<Section style={content}>
|
|
32
|
+
<Text style={heading}>You've been invited to join a team</Text>
|
|
33
|
+
<Text style={paragraph}>
|
|
34
|
+
{inviterName} has invited you to join <strong>{teamName}</strong>.
|
|
35
|
+
</Text>
|
|
36
|
+
<Text style={paragraph}>
|
|
37
|
+
Click the button below to accept the invitation and join the team:
|
|
38
|
+
</Text>
|
|
39
|
+
<Button href={inviteUrl} style={button}>
|
|
40
|
+
Accept Invitation
|
|
41
|
+
</Button>
|
|
42
|
+
<Hr style={hr} />
|
|
43
|
+
<Text style={footer}>
|
|
44
|
+
This invitation was sent to {recipientEmail}. If you weren't expecting this
|
|
45
|
+
invitation, you can safely ignore this email.
|
|
46
|
+
</Text>
|
|
47
|
+
</Section>
|
|
48
|
+
</Container>
|
|
49
|
+
</Body>
|
|
50
|
+
</Html>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const main = {
|
|
55
|
+
backgroundColor: "#f6f9fc",
|
|
56
|
+
fontFamily:
|
|
57
|
+
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const container = {
|
|
61
|
+
backgroundColor: "#ffffff",
|
|
62
|
+
margin: "0 auto",
|
|
63
|
+
padding: "20px 0 48px",
|
|
64
|
+
marginBottom: "64px",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const content = {
|
|
68
|
+
padding: "0 48px",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const heading = {
|
|
72
|
+
fontSize: "32px",
|
|
73
|
+
lineHeight: "1.3",
|
|
74
|
+
fontWeight: "700",
|
|
75
|
+
color: "#484848",
|
|
76
|
+
padding: "17px 0 0",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const paragraph = {
|
|
80
|
+
margin: "0 0 15px",
|
|
81
|
+
fontSize: "15px",
|
|
82
|
+
lineHeight: "1.4",
|
|
83
|
+
color: "#3c4149",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const button = {
|
|
87
|
+
backgroundColor: "#000000",
|
|
88
|
+
borderRadius: "5px",
|
|
89
|
+
color: "#fff",
|
|
90
|
+
fontSize: "16px",
|
|
91
|
+
fontWeight: "bold",
|
|
92
|
+
textDecoration: "none",
|
|
93
|
+
textAlign: "center" as const,
|
|
94
|
+
display: "block",
|
|
95
|
+
width: "100%",
|
|
96
|
+
padding: "12px",
|
|
97
|
+
margin: "20px 0",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const hr = {
|
|
101
|
+
borderColor: "#dfe1e4",
|
|
102
|
+
margin: "42px 0 26px",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const footer = {
|
|
106
|
+
color: "#8898aa",
|
|
107
|
+
fontSize: "12px",
|
|
108
|
+
lineHeight: "16px",
|
|
109
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cms0/transactional",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "./src/index.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./src/index.ts",
|
|
13
|
+
"import": "./src/index.ts",
|
|
14
|
+
"require": "./dist/index.js",
|
|
15
|
+
"default": "./src/index.ts"
|
|
16
|
+
},
|
|
17
|
+
"./*": {
|
|
18
|
+
"types": "./src/*.ts",
|
|
19
|
+
"import": "./src/*.ts",
|
|
20
|
+
"require": "./dist/*.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@react-email/components": "0.0.31",
|
|
25
|
+
"react": "19.0.0",
|
|
26
|
+
"react-dom": "19.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"@types/react": "^19.0.0",
|
|
31
|
+
"@types/react-dom": "^19.0.0",
|
|
32
|
+
"typescript": "^5.0.0",
|
|
33
|
+
"react-email": "3.0.3",
|
|
34
|
+
"@cms0/typescript-config": "0.0.1"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"dev": "email dev"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { PlunkSendEmailRequest, PlunkSendEmailResponse } from "./types";
|
|
2
|
+
|
|
3
|
+
const PLUNK_API_BASE_URL = "https://next-api.useplunk.com";
|
|
4
|
+
|
|
5
|
+
export class PlunkClient {
|
|
6
|
+
private apiKey: string;
|
|
7
|
+
|
|
8
|
+
constructor(apiKey?: string) {
|
|
9
|
+
this.apiKey = apiKey || process.env.PLUNK_API_KEY || "";
|
|
10
|
+
|
|
11
|
+
if (!this.apiKey) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
"Plunk API key is required. Set PLUNK_API_KEY environment variable or pass it to the constructor."
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async sendEmail(request: PlunkSendEmailRequest): Promise<PlunkSendEmailResponse> {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(`${PLUNK_API_BASE_URL}/v1/send`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: {
|
|
23
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify(request),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const data = await response.json() as PlunkSendEmailResponse;
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
data.error?.message || `Failed to send email: ${response.statusText}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return data;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof Error) {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
throw new Error("An unknown error occurred while sending email");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Export a default instance
|
|
48
|
+
let defaultClient: PlunkClient | null = null;
|
|
49
|
+
|
|
50
|
+
export function getDefaultClient(): PlunkClient {
|
|
51
|
+
if (!defaultClient) {
|
|
52
|
+
defaultClient = new PlunkClient();
|
|
53
|
+
}
|
|
54
|
+
return defaultClient;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function setDefaultClient(client: PlunkClient): void {
|
|
58
|
+
defaultClient = client;
|
|
59
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getDefaultClient, PlunkClient, setDefaultClient } from "./client";
|
|
2
|
+
import {
|
|
3
|
+
renderTeamInvite,
|
|
4
|
+
renderResetPassword,
|
|
5
|
+
} from "./templates";
|
|
6
|
+
import type {
|
|
7
|
+
TeamInviteProps,
|
|
8
|
+
ResetPasswordProps,
|
|
9
|
+
SendEmailOptions,
|
|
10
|
+
PlunkSendEmailResponse,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Send a team invitation email
|
|
15
|
+
*/
|
|
16
|
+
export async function sendTeamInvite(
|
|
17
|
+
to: string,
|
|
18
|
+
props: TeamInviteProps,
|
|
19
|
+
options?: SendEmailOptions
|
|
20
|
+
): Promise<PlunkSendEmailResponse> {
|
|
21
|
+
const client = getDefaultClient();
|
|
22
|
+
const html = await renderTeamInvite(props);
|
|
23
|
+
|
|
24
|
+
return await client.sendEmail({
|
|
25
|
+
to,
|
|
26
|
+
subject: `Join ${props.teamName}`,
|
|
27
|
+
body: html,
|
|
28
|
+
from: options?.from,
|
|
29
|
+
reply: options?.replyTo,
|
|
30
|
+
headers: options?.headers,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Send a password reset email
|
|
36
|
+
*/
|
|
37
|
+
export async function sendResetPassword(
|
|
38
|
+
to: string,
|
|
39
|
+
props: ResetPasswordProps,
|
|
40
|
+
options?: SendEmailOptions
|
|
41
|
+
): Promise<PlunkSendEmailResponse> {
|
|
42
|
+
const client = getDefaultClient();
|
|
43
|
+
const html = await renderResetPassword(props);
|
|
44
|
+
|
|
45
|
+
return await client.sendEmail({
|
|
46
|
+
to,
|
|
47
|
+
subject: "Reset your password",
|
|
48
|
+
body: html,
|
|
49
|
+
from: options?.from,
|
|
50
|
+
reply: options?.replyTo,
|
|
51
|
+
headers: options?.headers,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Export types and utilities
|
|
56
|
+
export type {
|
|
57
|
+
TeamInviteProps,
|
|
58
|
+
ResetPasswordProps,
|
|
59
|
+
SendEmailOptions,
|
|
60
|
+
PlunkSendEmailResponse,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export { PlunkClient, setDefaultClient };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { render } from "@react-email/components";
|
|
2
|
+
import TeamInviteEmail from "../emails/team-invite";
|
|
3
|
+
import ResetPasswordEmail from "../emails/reset-password";
|
|
4
|
+
import type {
|
|
5
|
+
TeamInviteProps,
|
|
6
|
+
ResetPasswordProps,
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
export async function renderTeamInvite(props: TeamInviteProps): Promise<string> {
|
|
10
|
+
return await render(<TeamInviteEmail {...props} />);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function renderResetPassword(props: ResetPasswordProps): Promise<string> {
|
|
14
|
+
return await render(<ResetPasswordEmail {...props} />);
|
|
15
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Plunk API types
|
|
2
|
+
export interface PlunkSendEmailRequest {
|
|
3
|
+
to: string | { name: string; email: string } | Array<string | { name: string; email: string }>;
|
|
4
|
+
subject: string;
|
|
5
|
+
body: string;
|
|
6
|
+
from?: string | { name: string; email: string };
|
|
7
|
+
name?: string;
|
|
8
|
+
subscribed?: boolean;
|
|
9
|
+
data?: Record<string, unknown>;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
reply?: string;
|
|
12
|
+
attachments?: Array<{
|
|
13
|
+
filename: string;
|
|
14
|
+
content: string; // base64
|
|
15
|
+
contentType: string;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PlunkSendEmailResponse {
|
|
20
|
+
success: boolean;
|
|
21
|
+
data?: {
|
|
22
|
+
emails: Array<{
|
|
23
|
+
contact: string;
|
|
24
|
+
email: string;
|
|
25
|
+
}>;
|
|
26
|
+
timestamp: string;
|
|
27
|
+
};
|
|
28
|
+
error?: {
|
|
29
|
+
code: string;
|
|
30
|
+
error: string;
|
|
31
|
+
message: string;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Email template props
|
|
37
|
+
export interface TeamInviteProps {
|
|
38
|
+
teamName: string;
|
|
39
|
+
inviterName: string;
|
|
40
|
+
inviteUrl: string;
|
|
41
|
+
recipientEmail: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ResetPasswordProps {
|
|
45
|
+
resetUrl: string;
|
|
46
|
+
userName?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Send email options
|
|
50
|
+
export interface SendEmailOptions {
|
|
51
|
+
from?: string | { name: string; email: string };
|
|
52
|
+
replyTo?: string;
|
|
53
|
+
headers?: Record<string, string>;
|
|
54
|
+
}
|