Skip to content

Commit f2ae139

Browse files
committed
improvement(emails): links, accounts, preview
1 parent 54c97fd commit f2ae139

File tree

7 files changed

+264
-8
lines changed

7 files changed

+264
-8
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import type { NextRequest } from 'next/server'
2+
import { NextResponse } from 'next/server'
3+
import {
4+
renderBatchInvitationEmail,
5+
renderCareersConfirmationEmail,
6+
renderCareersSubmissionEmail,
7+
renderCreditPurchaseEmail,
8+
renderEnterpriseSubscriptionEmail,
9+
renderFreeTierUpgradeEmail,
10+
renderHelpConfirmationEmail,
11+
renderInvitationEmail,
12+
renderOTPEmail,
13+
renderPasswordResetEmail,
14+
renderPaymentFailedEmail,
15+
renderPlanWelcomeEmail,
16+
renderUsageThresholdEmail,
17+
renderWelcomeEmail,
18+
renderWorkspaceInvitationEmail,
19+
} from '@/components/emails/render-email'
20+
21+
const emailTemplates = {
22+
// Auth emails
23+
otp: () => renderOTPEmail('123456', '[email protected]', 'email-verification'),
24+
'reset-password': () => renderPasswordResetEmail('John', 'https://sim.ai/reset?token=abc123'),
25+
welcome: () => renderWelcomeEmail('John'),
26+
27+
// Invitation emails
28+
invitation: () => renderInvitationEmail('Jane Doe', 'Acme Corp', 'https://sim.ai/invite/abc123'),
29+
'batch-invitation': () =>
30+
renderBatchInvitationEmail(
31+
'Jane Doe',
32+
'Acme Corp',
33+
'admin',
34+
[
35+
{ workspaceId: 'ws_123', workspaceName: 'Engineering', permission: 'write' },
36+
{ workspaceId: 'ws_456', workspaceName: 'Design', permission: 'read' },
37+
],
38+
'https://sim.ai/invite/abc123'
39+
),
40+
'workspace-invitation': () =>
41+
renderWorkspaceInvitationEmail(
42+
'John Smith',
43+
'Engineering Team',
44+
'https://sim.ai/workspace/invite/abc123'
45+
),
46+
47+
// Support emails
48+
'help-confirmation': () => renderHelpConfirmationEmail('feature_request', 2),
49+
50+
// Billing emails
51+
'usage-threshold': () =>
52+
renderUsageThresholdEmail({
53+
userName: 'John',
54+
planName: 'Pro',
55+
percentUsed: 75,
56+
currentUsage: 15,
57+
limit: 20,
58+
ctaLink: 'https://sim.ai/settings/billing',
59+
}),
60+
'enterprise-subscription': () => renderEnterpriseSubscriptionEmail('John'),
61+
'free-tier-upgrade': () =>
62+
renderFreeTierUpgradeEmail({
63+
userName: 'John',
64+
percentUsed: 90,
65+
currentUsage: 9,
66+
limit: 10,
67+
upgradeLink: 'https://sim.ai/settings/billing',
68+
}),
69+
'plan-welcome-pro': () =>
70+
renderPlanWelcomeEmail({
71+
planName: 'Pro',
72+
userName: 'John',
73+
loginLink: 'https://sim.ai/login',
74+
}),
75+
'plan-welcome-team': () =>
76+
renderPlanWelcomeEmail({
77+
planName: 'Team',
78+
userName: 'John',
79+
loginLink: 'https://sim.ai/login',
80+
}),
81+
'credit-purchase': () =>
82+
renderCreditPurchaseEmail({
83+
userName: 'John',
84+
amount: 50,
85+
newBalance: 75,
86+
}),
87+
'payment-failed': () =>
88+
renderPaymentFailedEmail({
89+
userName: 'John',
90+
amountDue: 20,
91+
lastFourDigits: '4242',
92+
billingPortalUrl: 'https://sim.ai/settings/billing',
93+
failureReason: 'Card declined',
94+
}),
95+
96+
// Careers emails
97+
'careers-confirmation': () => renderCareersConfirmationEmail('John Doe', 'Senior Engineer'),
98+
'careers-submission': () =>
99+
renderCareersSubmissionEmail({
100+
name: 'John Doe',
101+
102+
phone: '+1 (555) 123-4567',
103+
position: 'Senior Engineer',
104+
linkedin: 'https://linkedin.com/in/johndoe',
105+
portfolio: 'https://johndoe.dev',
106+
experience: '5-10',
107+
location: 'San Francisco, CA',
108+
message:
109+
'I have 10 years of experience building scalable distributed systems. Most recently, I led a team at a Series B startup where we scaled from 100K to 10M users.',
110+
}),
111+
} as const
112+
113+
type EmailTemplate = keyof typeof emailTemplates
114+
115+
export async function GET(request: NextRequest) {
116+
const { searchParams } = new URL(request.url)
117+
const template = searchParams.get('template') as EmailTemplate | null
118+
119+
if (!template) {
120+
const categories = {
121+
Auth: ['otp', 'reset-password', 'welcome'],
122+
Invitations: ['invitation', 'batch-invitation', 'workspace-invitation'],
123+
Support: ['help-confirmation'],
124+
Billing: [
125+
'usage-threshold',
126+
'enterprise-subscription',
127+
'free-tier-upgrade',
128+
'plan-welcome-pro',
129+
'plan-welcome-team',
130+
'credit-purchase',
131+
'payment-failed',
132+
],
133+
Careers: ['careers-confirmation', 'careers-submission'],
134+
}
135+
136+
const categoryHtml = Object.entries(categories)
137+
.map(
138+
([category, templates]) => `
139+
<h2 style="margin-top: 24px; margin-bottom: 12px; font-size: 14px; color: #666; text-transform: uppercase; letter-spacing: 0.5px;">${category}</h2>
140+
<ul style="list-style: none; padding: 0; margin: 0;">
141+
${templates.map((t) => `<li style="margin: 8px 0;"><a href="?template=${t}" style="color: #32bd7e; text-decoration: none; font-size: 16px;">${t}</a></li>`).join('')}
142+
</ul>
143+
`
144+
)
145+
.join('')
146+
147+
return new NextResponse(
148+
`<!DOCTYPE html>
149+
<html>
150+
<head>
151+
<title>Email Previews</title>
152+
<style>
153+
body { font-family: system-ui, -apple-system, sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; }
154+
h1 { color: #333; margin-bottom: 32px; }
155+
a:hover { text-decoration: underline; }
156+
</style>
157+
</head>
158+
<body>
159+
<h1>Email Templates</h1>
160+
${categoryHtml}
161+
</body>
162+
</html>`,
163+
{ headers: { 'Content-Type': 'text/html' } }
164+
)
165+
}
166+
167+
if (!(template in emailTemplates)) {
168+
return NextResponse.json({ error: `Unknown template: ${template}` }, { status: 400 })
169+
}
170+
171+
const html = await emailTemplates[template]()
172+
173+
return new NextResponse(html, {
174+
headers: { 'Content-Type': 'text/html' },
175+
})
176+
}

apps/sim/components/emails/billing/enterprise-subscription-email.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ export const EnterpriseSubscriptionEmail = ({
6767
<div style={baseStyles.divider} />
6868

6969
<Text style={{ ...baseStyles.footerText, textAlign: 'left' }}>
70-
Questions? Reply to this email or contact support.
70+
Questions? Reply to this email or contact us at{' '}
71+
<Link href={`mailto:${brand.supportEmail}`} style={baseStyles.link}>
72+
{brand.supportEmail}
73+
</Link>
7174
</Text>
7275
</Section>
7376
</Container>

apps/sim/components/emails/billing/payment-failed-email.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,9 @@ export function PaymentFailedEmail({
130130

131131
<Text style={{ ...baseStyles.footerText, textAlign: 'left' }}>
132132
Common issues: expired card, insufficient funds, or incorrect billing info. Need help?{' '}
133-
<Link href={`${baseUrl}/support`} style={baseStyles.link}>
134-
Contact support
133+
<Link href={`mailto:${brand.supportEmail}`} style={baseStyles.link}>
134+
{brand.supportEmail}
135135
</Link>
136-
.
137136
</Text>
138137
</Section>
139138
</Container>

apps/sim/components/emails/billing/plan-welcome-email.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export function PlanWelcomeEmail({ planName, userName, loginLink }: PlanWelcomeE
6060

6161
<Text style={baseStyles.paragraph}>
6262
Want help getting started?{' '}
63-
<Link href='https://cal.com/waleedlatif/15min' style={baseStyles.link}>
63+
<Link href='https://cal.com/emirkarabeg/sim-team' style={baseStyles.link}>
6464
Schedule a call
6565
</Link>{' '}
6666
with our team.

apps/sim/components/emails/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
export * from './base-styles'
22
export { BatchInvitationEmail } from './batch-invitation-email'
3+
export { CreditPurchaseEmail } from './billing/credit-purchase-email'
34
export { EnterpriseSubscriptionEmail } from './billing/enterprise-subscription-email'
5+
export { FreeTierUpgradeEmail } from './billing/free-tier-upgrade-email'
6+
export { PaymentFailedEmail } from './billing/payment-failed-email'
47
export { PlanWelcomeEmail } from './billing/plan-welcome-email'
58
export { UsageThresholdEmail } from './billing/usage-threshold-email'
9+
export { CareersConfirmationEmail } from './careers/careers-confirmation-email'
10+
export { CareersSubmissionEmail } from './careers/careers-submission-email'
611
export { default as EmailFooter } from './footer'
712
export { HelpConfirmationEmail } from './help-confirmation-email'
813
export { InvitationEmail } from './invitation-email'

apps/sim/components/emails/render-email.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ import {
99
ResetPasswordEmail,
1010
UsageThresholdEmail,
1111
WelcomeEmail,
12+
WorkspaceInvitationEmail,
1213
} from '@/components/emails'
1314
import CreditPurchaseEmail from '@/components/emails/billing/credit-purchase-email'
1415
import FreeTierUpgradeEmail from '@/components/emails/billing/free-tier-upgrade-email'
16+
import PaymentFailedEmail from '@/components/emails/billing/payment-failed-email'
17+
import CareersConfirmationEmail from '@/components/emails/careers/careers-confirmation-email'
18+
import CareersSubmissionEmail from '@/components/emails/careers/careers-submission-email'
1519
import { getBrandConfig } from '@/lib/branding/branding'
1620
import { getBaseUrl } from '@/lib/core/utils/urls'
1721

@@ -217,3 +221,73 @@ export async function renderCreditPurchaseEmail(params: {
217221
})
218222
)
219223
}
224+
225+
export async function renderWorkspaceInvitationEmail(
226+
inviterName: string,
227+
workspaceName: string,
228+
invitationLink: string
229+
): Promise<string> {
230+
return await render(
231+
WorkspaceInvitationEmail({
232+
inviterName,
233+
workspaceName,
234+
invitationLink,
235+
})
236+
)
237+
}
238+
239+
export async function renderPaymentFailedEmail(params: {
240+
userName?: string
241+
amountDue: number
242+
lastFourDigits?: string
243+
billingPortalUrl: string
244+
failureReason?: string
245+
}): Promise<string> {
246+
return await render(
247+
PaymentFailedEmail({
248+
userName: params.userName,
249+
amountDue: params.amountDue,
250+
lastFourDigits: params.lastFourDigits,
251+
billingPortalUrl: params.billingPortalUrl,
252+
failureReason: params.failureReason,
253+
})
254+
)
255+
}
256+
257+
export async function renderCareersConfirmationEmail(
258+
name: string,
259+
position: string
260+
): Promise<string> {
261+
return await render(
262+
CareersConfirmationEmail({
263+
name,
264+
position,
265+
})
266+
)
267+
}
268+
269+
export async function renderCareersSubmissionEmail(params: {
270+
name: string
271+
email: string
272+
phone?: string
273+
position: string
274+
linkedin?: string
275+
portfolio?: string
276+
experience: string
277+
location: string
278+
message: string
279+
}): Promise<string> {
280+
return await render(
281+
CareersSubmissionEmail({
282+
name: params.name,
283+
email: params.email,
284+
phone: params.phone,
285+
position: params.position,
286+
linkedin: params.linkedin,
287+
portfolio: params.portfolio,
288+
experience: params.experience,
289+
location: params.location,
290+
message: params.message,
291+
})
292+
)
293+
}

apps/sim/components/emails/welcome-email.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,10 @@ export function WelcomeEmail({ userName }: WelcomeEmailProps) {
5454
</Link>
5555

5656
<Text style={baseStyles.paragraph}>
57-
If you have any questions or feedback, just reply to this email — I read every
58-
message.
57+
If you have any questions or feedback, just reply to this email. I read every message!
5958
</Text>
6059

61-
<Text style={baseStyles.paragraph}> Emir, Co-founder & CEO of {brand.name}</Text>
60+
<Text style={baseStyles.paragraph}>- Emir, co-founder of {brand.name}</Text>
6261

6362
{/* Divider */}
6463
<div style={baseStyles.divider} />

0 commit comments

Comments
 (0)