Skip to content

Commit 6fd6f92

Browse files
authored
feat(mailer): consolidated all emailing to mailer service, added support for Azure ACS (#1054)
* feat(mailer): consolidated all emailing to mailer service, added support for Azure ACS * fix batch invitation email template * cleanup * improvement(emails): add help template instead of doing it inline
1 parent 7530fb9 commit 6fd6f92

13 files changed

Lines changed: 1147 additions & 569 deletions

File tree

apps/sim/app/api/help/route.ts

Lines changed: 33 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { type NextRequest, NextResponse } from 'next/server'
2-
import { Resend } from 'resend'
32
import { z } from 'zod'
3+
import { renderHelpConfirmationEmail } from '@/components/emails'
44
import { getSession } from '@/lib/auth'
5+
import { sendEmail } from '@/lib/email/mailer'
56
import { env } from '@/lib/env'
67
import { createLogger } from '@/lib/logs/console/logger'
78
import { getEmailDomain } from '@/lib/urls/utils'
89

9-
const resend = env.RESEND_API_KEY ? new Resend(env.RESEND_API_KEY) : null
1010
const logger = createLogger('HelpAPI')
1111

1212
const helpFormSchema = z.object({
@@ -28,18 +28,6 @@ export async function POST(req: NextRequest) {
2828

2929
const email = session.user.email
3030

31-
// Check if Resend API key is configured
32-
if (!resend) {
33-
logger.error(`[${requestId}] RESEND_API_KEY not configured`)
34-
return NextResponse.json(
35-
{
36-
error:
37-
'Email service not configured. Please set RESEND_API_KEY in environment variables.',
38-
},
39-
{ status: 500 }
40-
)
41-
}
42-
4331
// Handle multipart form data
4432
const formData = await req.formData()
4533

@@ -54,18 +42,18 @@ export async function POST(req: NextRequest) {
5442
})
5543

5644
// Validate the form data
57-
const result = helpFormSchema.safeParse({
45+
const validationResult = helpFormSchema.safeParse({
5846
subject,
5947
message,
6048
type,
6149
})
6250

63-
if (!result.success) {
51+
if (!validationResult.success) {
6452
logger.warn(`[${requestId}] Invalid help request data`, {
65-
errors: result.error.format(),
53+
errors: validationResult.error.format(),
6654
})
6755
return NextResponse.json(
68-
{ error: 'Invalid request data', details: result.error.format() },
56+
{ error: 'Invalid request data', details: validationResult.error.format() },
6957
{ status: 400 }
7058
)
7159
}
@@ -103,63 +91,60 @@ ${message}
10391
emailText += `\n\n${images.length} image(s) attached.`
10492
}
10593

106-
// Send email using Resend
107-
const { error } = await resend.emails.send({
108-
from: `Sim <noreply@${env.EMAIL_DOMAIN || getEmailDomain()}>`,
94+
const emailResult = await sendEmail({
10995
to: [`help@${env.EMAIL_DOMAIN || getEmailDomain()}`],
11096
subject: `[${type.toUpperCase()}] ${subject}`,
111-
replyTo: email,
11297
text: emailText,
98+
from: `${env.SENDER_NAME || 'Sim'} <noreply@${env.EMAIL_DOMAIN || getEmailDomain()}>`,
99+
replyTo: email,
100+
emailType: 'transactional',
113101
attachments: images.map((image) => ({
114102
filename: image.filename,
115103
content: image.content.toString('base64'),
116104
contentType: image.contentType,
117-
disposition: 'attachment', // Explicitly set as attachment
105+
disposition: 'attachment',
118106
})),
119107
})
120108

121-
if (error) {
122-
logger.error(`[${requestId}] Error sending help request email`, error)
109+
if (!emailResult.success) {
110+
logger.error(`[${requestId}] Error sending help request email`, emailResult.message)
123111
return NextResponse.json({ error: 'Failed to send email' }, { status: 500 })
124112
}
125113

126114
logger.info(`[${requestId}] Help request email sent successfully`)
127115

128116
// Send confirmation email to the user
129-
await resend.emails
130-
.send({
131-
from: `Sim <noreply@${env.EMAIL_DOMAIN || getEmailDomain()}>`,
117+
try {
118+
const confirmationHtml = await renderHelpConfirmationEmail(
119+
email,
120+
type as 'bug' | 'feedback' | 'feature_request' | 'other',
121+
images.length
122+
)
123+
124+
await sendEmail({
132125
to: [email],
133126
subject: `Your ${type} request has been received: ${subject}`,
134-
text: `
135-
Hello,
136-
137-
Thank you for your ${type} submission. We've received your request and will get back to you as soon as possible.
138-
139-
Your message:
140-
${message}
141-
142-
${images.length > 0 ? `You attached ${images.length} image(s).` : ''}
143-
144-
Best regards,
145-
The Sim Team
146-
`,
127+
html: confirmationHtml,
128+
from: `${env.SENDER_NAME || 'Sim'} <noreply@${env.EMAIL_DOMAIN || getEmailDomain()}>`,
147129
replyTo: `help@${env.EMAIL_DOMAIN || getEmailDomain()}`,
130+
emailType: 'transactional',
148131
})
149-
.catch((err) => {
150-
logger.warn(`[${requestId}] Failed to send confirmation email`, err)
151-
})
132+
} catch (err) {
133+
logger.warn(`[${requestId}] Failed to send confirmation email`, err)
134+
}
152135

153136
return NextResponse.json(
154137
{ success: true, message: 'Help request submitted successfully' },
155138
{ status: 200 }
156139
)
157140
} catch (error) {
158-
// Check if error is related to missing API key
159-
if (error instanceof Error && error.message.includes('API key')) {
160-
logger.error(`[${requestId}] API key configuration error`, error)
141+
if (error instanceof Error && error.message.includes('not configured')) {
142+
logger.error(`[${requestId}] Email service configuration error`, error)
161143
return NextResponse.json(
162-
{ error: 'Email service configuration error. Please check your RESEND_API_KEY.' },
144+
{
145+
error:
146+
'Email service configuration error. Please check your email service configuration.',
147+
},
163148
{ status: 500 }
164149
)
165150
}

apps/sim/app/api/workspaces/invitations/route.ts

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { randomUUID } from 'crypto'
22
import { render } from '@react-email/render'
33
import { and, eq, inArray } from 'drizzle-orm'
44
import { type NextRequest, NextResponse } from 'next/server'
5-
import { Resend } from 'resend'
65
import { WorkspaceInvitationEmail } from '@/components/emails/workspace-invitation'
76
import { getSession } from '@/lib/auth'
7+
import { sendEmail } from '@/lib/email/mailer'
88
import { env } from '@/lib/env'
99
import { createLogger } from '@/lib/logs/console/logger'
1010
import { getEmailDomain } from '@/lib/urls/utils'
@@ -20,7 +20,6 @@ import {
2020
export const dynamic = 'force-dynamic'
2121

2222
const logger = createLogger('WorkspaceInvitationsAPI')
23-
const resend = env.RESEND_API_KEY ? new Resend(env.RESEND_API_KEY) : null
2423

2524
type PermissionType = (typeof permissionTypeEnum.enumValues)[number]
2625

@@ -241,30 +240,25 @@ async function sendInvitationEmail({
241240
})
242241
)
243242

244-
if (!resend) {
245-
logger.error('RESEND_API_KEY not configured')
246-
return NextResponse.json(
247-
{
248-
error:
249-
'Email service not configured. Please set RESEND_API_KEY in environment variables.',
250-
},
251-
{ status: 500 }
252-
)
253-
}
254-
255243
const emailDomain = env.EMAIL_DOMAIN || getEmailDomain()
256-
const fromAddress = `noreply@${emailDomain}`
244+
const fromAddress = `${env.SENDER_NAME || 'Sim'} <noreply@${emailDomain}>`
257245

258246
logger.info(`Attempting to send email from ${fromAddress} to ${to}`)
259247

260-
const result = await resend.emails.send({
261-
from: fromAddress,
248+
const result = await sendEmail({
262249
to,
263250
subject: `You've been invited to join "${workspaceName}" on Sim`,
264251
html: emailHtml,
252+
from: fromAddress,
253+
emailType: 'transactional',
254+
useCustomFromFormat: true,
265255
})
266256

267-
logger.info(`Invitation email sent successfully to ${to}`, { result })
257+
if (result.success) {
258+
logger.info(`Invitation email sent successfully to ${to}`, { result })
259+
} else {
260+
logger.error(`Failed to send invitation email to ${to}`, { error: result.message })
261+
}
268262
} catch (error) {
269263
logger.error('Error sending invitation email:', error)
270264
// Continue even if email fails - the invitation is still created

apps/sim/components/emails/base-styles.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const baseStyles = {
3131
},
3232
button: {
3333
display: 'inline-block',
34-
backgroundColor: 'var(--brand-primary-hover-hex)',
34+
backgroundColor: '#802FFF',
3535
color: '#ffffff',
3636
fontWeight: 'bold',
3737
fontSize: '16px',
@@ -42,7 +42,7 @@ export const baseStyles = {
4242
margin: '20px 0',
4343
},
4444
link: {
45-
color: 'var(--brand-primary-hover-hex)',
45+
color: '#802FFF',
4646
textDecoration: 'underline',
4747
},
4848
footer: {
@@ -79,7 +79,7 @@ export const baseStyles = {
7979
width: '249px',
8080
},
8181
sectionCenter: {
82-
borderBottom: '1px solid var(--brand-primary-hover-hex)',
82+
borderBottom: '1px solid #802FFF',
8383
width: '102px',
8484
},
8585
}

0 commit comments

Comments
 (0)