@geekmidas/emailkit 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/README.md +423 -0
- package/dist/__tests__/client.spec.cjs +13218 -0
- package/dist/__tests__/client.spec.mjs +13222 -0
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/client-7k6es7pg.cjs +68 -0
- package/dist/client-B7LjRjRM.mjs +56 -0
- package/dist/client.cjs +4 -0
- package/dist/client.mjs +3 -0
- package/dist/index.cjs +4 -0
- package/dist/index.mjs +3 -0
- package/dist/magic-string.es-BGRTPV9O.mjs +1014 -0
- package/dist/magic-string.es-BrLHy2bw.cjs +1015 -0
- package/dist/templates.cjs +176 -0
- package/dist/templates.mjs +171 -0
- package/dist/types.cjs +0 -0
- package/dist/types.mjs +0 -0
- package/package.json +39 -0
- package/src/__tests__/client.spec.tsx +182 -0
- package/src/client.ts +87 -0
- package/src/index.ts +11 -0
- package/src/templates.tsx +182 -0
- package/src/types.ts +94 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { CSSProperties, ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
// Common email styling utilities
|
|
5
|
+
export const emailStyles = {
|
|
6
|
+
container: {
|
|
7
|
+
fontFamily:
|
|
8
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
9
|
+
lineHeight: 1.6,
|
|
10
|
+
color: '#333',
|
|
11
|
+
maxWidth: '600px',
|
|
12
|
+
margin: '0 auto',
|
|
13
|
+
padding: '20px',
|
|
14
|
+
} as CSSProperties,
|
|
15
|
+
|
|
16
|
+
heading: {
|
|
17
|
+
color: '#2c3e50',
|
|
18
|
+
marginBottom: '20px',
|
|
19
|
+
} as CSSProperties,
|
|
20
|
+
|
|
21
|
+
button: {
|
|
22
|
+
display: 'inline-block',
|
|
23
|
+
padding: '12px 24px',
|
|
24
|
+
backgroundColor: '#3498db',
|
|
25
|
+
color: '#ffffff',
|
|
26
|
+
textDecoration: 'none',
|
|
27
|
+
borderRadius: '4px',
|
|
28
|
+
fontWeight: 'bold',
|
|
29
|
+
} as CSSProperties,
|
|
30
|
+
|
|
31
|
+
footer: {
|
|
32
|
+
marginTop: '40px',
|
|
33
|
+
paddingTop: '20px',
|
|
34
|
+
borderTop: '1px solid #eee',
|
|
35
|
+
fontSize: '14px',
|
|
36
|
+
color: '#666',
|
|
37
|
+
} as CSSProperties,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Base layout component for emails
|
|
41
|
+
export interface EmailLayoutProps {
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
preheader?: string;
|
|
44
|
+
title?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function EmailLayout({
|
|
48
|
+
children,
|
|
49
|
+
preheader,
|
|
50
|
+
title,
|
|
51
|
+
}: EmailLayoutProps): ReactElement {
|
|
52
|
+
return (
|
|
53
|
+
<html>
|
|
54
|
+
<head>
|
|
55
|
+
<meta charSet="utf-8" />
|
|
56
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
57
|
+
{title && <title>{title}</title>}
|
|
58
|
+
{preheader && (
|
|
59
|
+
<div style={{ display: 'none', maxHeight: 0, overflow: 'hidden' }}>
|
|
60
|
+
{preheader}
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
</head>
|
|
64
|
+
<body style={{ margin: 0, padding: 0, backgroundColor: '#f4f4f4' }}>
|
|
65
|
+
<div style={emailStyles.container}>{children}</div>
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Example welcome email template
|
|
72
|
+
export interface WelcomeEmailProps {
|
|
73
|
+
name: string;
|
|
74
|
+
confirmationUrl?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function WelcomeEmail({
|
|
78
|
+
name,
|
|
79
|
+
confirmationUrl,
|
|
80
|
+
}: WelcomeEmailProps): ReactElement {
|
|
81
|
+
return (
|
|
82
|
+
<EmailLayout
|
|
83
|
+
title="Welcome!"
|
|
84
|
+
preheader={`Welcome to our service, ${name}!`}
|
|
85
|
+
>
|
|
86
|
+
<h1 style={emailStyles.heading}>Welcome, {name}!</h1>
|
|
87
|
+
<p>
|
|
88
|
+
We're excited to have you on board. Your account has been successfully
|
|
89
|
+
created.
|
|
90
|
+
</p>
|
|
91
|
+
{confirmationUrl && (
|
|
92
|
+
<>
|
|
93
|
+
<p>Please confirm your email address by clicking the button below:</p>
|
|
94
|
+
<p style={{ textAlign: 'center', margin: '30px 0' }}>
|
|
95
|
+
<a href={confirmationUrl} style={emailStyles.button}>
|
|
96
|
+
Confirm Email
|
|
97
|
+
</a>
|
|
98
|
+
</p>
|
|
99
|
+
</>
|
|
100
|
+
)}
|
|
101
|
+
<div style={emailStyles.footer}>
|
|
102
|
+
<p>If you have any questions, feel free to reply to this email.</p>
|
|
103
|
+
<p>
|
|
104
|
+
Best regards,
|
|
105
|
+
<br />
|
|
106
|
+
The Team
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
</EmailLayout>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Example password reset template
|
|
114
|
+
export interface PasswordResetEmailProps {
|
|
115
|
+
name: string;
|
|
116
|
+
resetUrl: string;
|
|
117
|
+
expiresIn: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function PasswordResetEmail({
|
|
121
|
+
name,
|
|
122
|
+
resetUrl,
|
|
123
|
+
expiresIn,
|
|
124
|
+
}: PasswordResetEmailProps): ReactElement {
|
|
125
|
+
return (
|
|
126
|
+
<EmailLayout title="Password Reset Request" preheader="Reset your password">
|
|
127
|
+
<h1 style={emailStyles.heading}>Password Reset Request</h1>
|
|
128
|
+
<p>Hi {name},</p>
|
|
129
|
+
<p>
|
|
130
|
+
We received a request to reset your password. Click the button below to
|
|
131
|
+
create a new password:
|
|
132
|
+
</p>
|
|
133
|
+
<p style={{ textAlign: 'center', margin: '30px 0' }}>
|
|
134
|
+
<a href={resetUrl} style={emailStyles.button}>
|
|
135
|
+
Reset Password
|
|
136
|
+
</a>
|
|
137
|
+
</p>
|
|
138
|
+
<p>
|
|
139
|
+
This link will expire in {expiresIn}. If you didn't request a password
|
|
140
|
+
reset, you can safely ignore this email.
|
|
141
|
+
</p>
|
|
142
|
+
<div style={emailStyles.footer}>
|
|
143
|
+
<p>For security reasons, this link can only be used once.</p>
|
|
144
|
+
</div>
|
|
145
|
+
</EmailLayout>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Example notification template
|
|
150
|
+
export interface NotificationEmailProps {
|
|
151
|
+
name: string;
|
|
152
|
+
title: string;
|
|
153
|
+
message: string;
|
|
154
|
+
actionUrl?: string;
|
|
155
|
+
actionText?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function NotificationEmail({
|
|
159
|
+
name,
|
|
160
|
+
title,
|
|
161
|
+
message,
|
|
162
|
+
actionUrl,
|
|
163
|
+
actionText = 'View Details',
|
|
164
|
+
}: NotificationEmailProps): ReactElement {
|
|
165
|
+
return (
|
|
166
|
+
<EmailLayout title={title} preheader={message}>
|
|
167
|
+
<h1 style={emailStyles.heading}>{title}</h1>
|
|
168
|
+
<p>Hi {name},</p>
|
|
169
|
+
<p>{message}</p>
|
|
170
|
+
{actionUrl && (
|
|
171
|
+
<p style={{ textAlign: 'center', margin: '30px 0' }}>
|
|
172
|
+
<a href={actionUrl} style={emailStyles.button}>
|
|
173
|
+
{actionText}
|
|
174
|
+
</a>
|
|
175
|
+
</p>
|
|
176
|
+
)}
|
|
177
|
+
<div style={emailStyles.footer}>
|
|
178
|
+
<p>This is an automated notification from our system.</p>
|
|
179
|
+
</div>
|
|
180
|
+
</EmailLayout>
|
|
181
|
+
);
|
|
182
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Address } from 'nodemailer/lib/mailer';
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface EmailOptions {
|
|
5
|
+
from: string | Address;
|
|
6
|
+
to: string | string[] | Address[];
|
|
7
|
+
cc?: string | string[] | Address[];
|
|
8
|
+
bcc?: string | string[] | Address[];
|
|
9
|
+
subject: string;
|
|
10
|
+
replyTo?: string | Address;
|
|
11
|
+
attachments?: Attachment[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Attachment {
|
|
15
|
+
filename: string;
|
|
16
|
+
content?: string | Buffer;
|
|
17
|
+
path?: string;
|
|
18
|
+
contentType?: string;
|
|
19
|
+
cid?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PlainEmailOptions extends EmailOptions {
|
|
23
|
+
text?: string;
|
|
24
|
+
html?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export interface SMTPConfig {
|
|
29
|
+
host: string;
|
|
30
|
+
port: number;
|
|
31
|
+
secure?: boolean;
|
|
32
|
+
auth?: {
|
|
33
|
+
user: string;
|
|
34
|
+
pass: string;
|
|
35
|
+
};
|
|
36
|
+
tls?: {
|
|
37
|
+
rejectUnauthorized?: boolean;
|
|
38
|
+
servername?: string;
|
|
39
|
+
};
|
|
40
|
+
pool?: boolean;
|
|
41
|
+
maxConnections?: number;
|
|
42
|
+
maxMessages?: number;
|
|
43
|
+
rateLimit?: number;
|
|
44
|
+
logger?: boolean;
|
|
45
|
+
debug?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
export interface SendResult {
|
|
50
|
+
messageId: string;
|
|
51
|
+
accepted: string[];
|
|
52
|
+
rejected: string[];
|
|
53
|
+
response: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface EmailTemplate<T = any> {
|
|
57
|
+
(props: T): ReactElement;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Extract props type from a template function
|
|
61
|
+
export type TemplateProps<T> = T extends EmailTemplate<infer P> ? P : never;
|
|
62
|
+
|
|
63
|
+
// Template record type for type safety
|
|
64
|
+
export type TemplateRecord = Record<string, EmailTemplate<any>>;
|
|
65
|
+
|
|
66
|
+
// Extract template names from template record
|
|
67
|
+
export type TemplateNames<T extends TemplateRecord> = keyof T;
|
|
68
|
+
|
|
69
|
+
// Extract props for a specific template
|
|
70
|
+
export type TemplatePropsFor<
|
|
71
|
+
T extends TemplateRecord,
|
|
72
|
+
K extends TemplateNames<T>,
|
|
73
|
+
> = TemplateProps<T[K]>;
|
|
74
|
+
|
|
75
|
+
export interface EmailClientConfig<T extends TemplateRecord = TemplateRecord> {
|
|
76
|
+
smtp: SMTPConfig;
|
|
77
|
+
templates: T;
|
|
78
|
+
defaults?: {
|
|
79
|
+
from?: string | Address;
|
|
80
|
+
replyTo?: string | Address;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface EmailClient<T extends TemplateRecord = TemplateRecord> {
|
|
85
|
+
send(options: PlainEmailOptions): Promise<SendResult>;
|
|
86
|
+
sendTemplate<K extends TemplateNames<T>>(
|
|
87
|
+
template: K,
|
|
88
|
+
options: Omit<EmailOptions, 'template'> & {
|
|
89
|
+
props: TemplatePropsFor<T, K>;
|
|
90
|
+
},
|
|
91
|
+
): Promise<SendResult>;
|
|
92
|
+
verify(): Promise<boolean>;
|
|
93
|
+
close(): Promise<void>;
|
|
94
|
+
}
|