1
+ import type { ReactNode } from "react" ;
1
2
import type {
2
3
StripeBillingCycle ,
3
4
StripeProduct ,
4
5
} from "../db/models/workspace.ts" ;
5
6
import { Resend } from "resend" ;
7
+ import { render } from "@react-email/render" ;
6
8
import settings from "../settings.ts" ;
7
9
10
+ import OtpEmail from "./templates/OtpEmail.tsx" ;
11
+ import WelcomeEmail from "./templates/WelcomeEmail.tsx" ;
12
+ import InvitationEmail from "./templates/InvitationEmail.tsx" ;
13
+ import UpgradeEmail from "./templates/UpgradeEmail.tsx" ;
14
+ import DowngradeEmail from "./templates/DowngradeEmail.tsx" ;
15
+
8
16
export type SendEmail = ( options : {
9
17
to : string [ ] ;
10
18
cc ?: string [ ] ;
11
19
bcc ?: string [ ] ;
12
20
subject : string ;
13
21
text : string ;
14
- html ?: string ;
22
+ react ?: ReactNode ;
15
23
} ) => Promise < void > ;
16
24
17
25
const resend = new Resend ( settings . EMAIL . RESEND_API_KEY ) ;
@@ -22,7 +30,7 @@ const sendEmailWithResend: SendEmail = async (options: {
22
30
bcc ?: string [ ] ;
23
31
subject : string ;
24
32
text : string ;
25
- html ?: string ;
33
+ react ?: ReactNode ;
26
34
} ) => {
27
35
const response = await resend . emails . send ( {
28
36
from : `${ settings . EMAIL . FROM_EMAIL } <${ settings . EMAIL . FROM_EMAIL } >` ,
@@ -31,7 +39,7 @@ const sendEmailWithResend: SendEmail = async (options: {
31
39
bcc : options . bcc ,
32
40
subject : options . subject ,
33
41
text : options . text ,
34
- html : options . html ,
42
+ react : options . react ,
35
43
} ) ;
36
44
37
45
if ( response . error ) {
@@ -46,7 +54,7 @@ const sendEmailWithConsole: SendEmail = async (options: {
46
54
bcc ?: string [ ] ;
47
55
subject : string ;
48
56
text : string ;
49
- html ?: string ;
57
+ react ?: ReactNode ;
50
58
} ) => {
51
59
console . info ( `Sending email to ${ options . to } : ${ options . subject } ` ) ;
52
60
console . info ( options . text ) ;
@@ -60,52 +68,35 @@ function getSendEmail() {
60
68
return sendEmailWithResend ;
61
69
}
62
70
63
- export function sendOtpEmail ( email : string , otp : string ) {
71
+ export async function sendOtpEmail ( email : string , otp : string ) {
72
+ const emailPlainText = await render ( OtpEmail ( { otp } ) , {
73
+ plainText : true ,
74
+ } ) ;
75
+
64
76
const sendEmail = getSendEmail ( ) ;
65
77
sendEmail ( {
66
78
to : [ email ] ,
67
79
subject : "Your One-Time Password (OTP)" ,
68
- text : `Hi there,
69
-
70
- You've requested a one-time password to access your account. Please use the code below to complete your authentication:
71
-
72
- **${ otp } **
73
-
74
- This code is valid for a limited time and can only be used once. For your security, please do not share this code with anyone.
75
-
76
- If you didn't request this code, please ignore this email or contact our support team if you have concerns about your account security.
77
-
78
- Best regards,
79
- The Team` ,
80
+ text : emailPlainText ,
81
+ react : OtpEmail ( { otp } ) ,
80
82
} ) ;
81
83
}
82
84
83
- export function sendWelcomeEmail ( email : string ) {
85
+ export async function sendWelcomeEmail ( email : string ) {
86
+ const emailPlainText = await render ( WelcomeEmail ( ) , {
87
+ plainText : true ,
88
+ } ) ;
89
+
84
90
const sendEmail = getSendEmail ( ) ;
85
91
sendEmail ( {
86
92
to : [ email ] ,
87
93
subject : "Welcome to our platform!" ,
88
- text : `Hi there,
89
-
90
- Welcome to our platform! We're thrilled to have you join our community.
91
-
92
- Your account has been successfully created and you're all set to get started. Here's what you can do next:
93
-
94
- • Explore the dashboard and familiarize yourself with the interface
95
- • Set up your profile and preferences
96
- • Create your first workspace or join an existing one
97
- • Invite team members to collaborate with you
98
-
99
- If you have any questions or need assistance getting started, our support team is here to help. Don't hesitate to reach out!
100
-
101
- We're excited to see what you'll accomplish with our platform.
102
-
103
- Best regards,
104
- The Team` ,
94
+ text : emailPlainText ,
95
+ react : WelcomeEmail ( ) ,
105
96
} ) ;
106
97
}
107
98
108
- export function sendInvitationEmail (
99
+ export async function sendInvitationEmail (
109
100
email : string ,
110
101
workspaceName : string ,
111
102
invitationUuid : string ,
@@ -118,27 +109,28 @@ export function sendInvitationEmail(
118
109
119
110
const invitationLink = `${ returnUrl } ?${ searchParams . toString ( ) } ` ;
120
111
112
+ const emailPlainText = await render (
113
+ InvitationEmail ( {
114
+ workspaceName,
115
+ invitationLink,
116
+ } ) ,
117
+ {
118
+ plainText : true ,
119
+ } ,
120
+ ) ;
121
+
121
122
sendEmail ( {
122
123
to : [ email ] ,
123
124
subject : `Invitation to join ${ workspaceName } ` ,
124
- text : `Hi there,
125
-
126
- You've been invited to join "${ workspaceName } "!
127
-
128
- We're excited to have you as part of our team. To get started, simply click the link below to accept your invitation and set up your account:
129
-
130
- ${ invitationLink }
131
-
132
- This invitation link is unique to you and will expire after a certain period for security reasons. If you have any questions or need assistance, please don't hesitate to reach out to your team administrator.
133
-
134
- We look forward to collaborating with you!
135
-
136
- Best regards,
137
- The Team` ,
125
+ text : emailPlainText ,
126
+ react : InvitationEmail ( {
127
+ workspaceName,
128
+ invitationLink,
129
+ } ) ,
138
130
} ) ;
139
131
}
140
132
141
- export function sendSubscriptionUpgradedEmail (
133
+ export async function sendSubscriptionUpgradedEmail (
142
134
payload : {
143
135
email : string ;
144
136
workspaceName : string ;
@@ -152,31 +144,23 @@ export function sendSubscriptionUpgradedEmail(
152
144
} ;
153
145
} ,
154
146
) {
147
+ const emailPlainText = await render (
148
+ UpgradeEmail ( payload ) ,
149
+ {
150
+ plainText : true ,
151
+ } ,
152
+ ) ;
153
+
155
154
const sendEmail = getSendEmail ( ) ;
156
155
sendEmail ( {
157
156
to : [ payload . email ] ,
158
157
subject : "Subscription upgraded" ,
159
- text : `Hi there,
160
-
161
- Your subscription for workspace "${ payload . workspaceName } " has been successfully upgraded!
162
-
163
- Previous subscription: ${ payload . oldSubscription . product } (${
164
- payload . oldSubscription . billingCycle ?? "Custom billing cycle"
165
- } )
166
- New subscription: ${ payload . newSubscription . product } (${
167
- payload . newSubscription . billingCycle ?? "Custom billing cycle"
168
- } )
169
-
170
- This change is effective immediately, and you now have access to all the features included in your new subscription.
171
-
172
- If you have any questions about your upgraded subscription, please don't hesitate to contact our support team.
173
-
174
- Best regards,
175
- The Team` ,
158
+ text : emailPlainText ,
159
+ react : UpgradeEmail ( payload ) ,
176
160
} ) ;
177
161
}
178
162
179
- export function sendSubscriptionDowngradedEmail (
163
+ export async function sendSubscriptionDowngradedEmail (
180
164
payload : {
181
165
email : string ;
182
166
workspaceName : string ;
@@ -191,27 +175,18 @@ export function sendSubscriptionDowngradedEmail(
191
175
newSubscriptionDate : string ;
192
176
} ,
193
177
) {
178
+ const emailPlainText = await render (
179
+ DowngradeEmail ( payload ) ,
180
+ {
181
+ plainText : true ,
182
+ } ,
183
+ ) ;
184
+
194
185
const sendEmail = getSendEmail ( ) ;
195
186
sendEmail ( {
196
187
to : [ payload . email ] ,
197
188
subject : "Subscription downgraded" ,
198
- text : `Hi there,
199
-
200
- Your subscription for workspace "${ payload . workspaceName } " has been scheduled for downgrade.
201
-
202
- Current subscription: ${ payload . oldSubscription . product } (${
203
- payload . oldSubscription . billingCycle ?? "Custom billing cycle"
204
- } )
205
- New subscription: ${ payload . newSubscription . product } (${
206
- payload . newSubscription . billingCycle ?? "Custom billing cycle"
207
- } )
208
- Effective date: ${ payload . newSubscriptionDate }
209
-
210
- Your current subscription will remain active until the end of your billing period, and the new subscription will take effect on ${ payload . newSubscriptionDate } .
211
-
212
- If you have any questions, please don't hesitate to contact our support team.
213
-
214
- Best regards,
215
- The Team` ,
189
+ text : emailPlainText ,
190
+ react : DowngradeEmail ( payload ) ,
216
191
} ) ;
217
192
}
0 commit comments