Skip to content

Commit 6fcc661

Browse files
committed
feat(resend): expand integration with contacts, domains, and enhanced email ops
1 parent 4ccb573 commit 6fcc661

File tree

13 files changed

+917
-43
lines changed

13 files changed

+917
-43
lines changed

apps/sim/app/api/tools/mail/send/route.ts

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,26 @@ export const dynamic = 'force-dynamic'
1010
const logger = createLogger('MailSendAPI')
1111

1212
const MailSendSchema = z.object({
13-
fromAddress: z.string().email('Invalid from email address').min(1, 'From address is required'),
14-
to: z.string().email('Invalid email address').min(1, 'To email is required'),
13+
fromAddress: z.string().min(1, 'From address is required'),
14+
to: z.string().min(1, 'To email is required'),
1515
subject: z.string().min(1, 'Subject is required'),
1616
body: z.string().min(1, 'Email body is required'),
1717
contentType: z.enum(['text', 'html']).optional().nullable(),
1818
resendApiKey: z.string().min(1, 'Resend API key is required'),
19+
cc: z
20+
.union([z.string().email(), z.array(z.string().email())])
21+
.optional()
22+
.nullable(),
23+
bcc: z
24+
.union([z.string().email(), z.array(z.string().email())])
25+
.optional()
26+
.nullable(),
27+
replyTo: z
28+
.union([z.string().email(), z.array(z.string().email())])
29+
.optional()
30+
.nullable(),
31+
scheduledAt: z.string().datetime().optional().nullable(),
32+
tags: z.string().optional().nullable(),
1933
})
2034

2135
export async function POST(request: NextRequest) {
@@ -52,23 +66,50 @@ export async function POST(request: NextRequest) {
5266
const resend = new Resend(validatedData.resendApiKey)
5367

5468
const contentType = validatedData.contentType || 'text'
55-
const emailData =
56-
contentType === 'html'
57-
? {
58-
from: validatedData.fromAddress,
59-
to: validatedData.to,
60-
subject: validatedData.subject,
61-
html: validatedData.body,
62-
text: validatedData.body.replace(/<[^>]*>/g, ''), // Strip HTML for text version
63-
}
64-
: {
65-
from: validatedData.fromAddress,
66-
to: validatedData.to,
67-
subject: validatedData.subject,
68-
text: validatedData.body,
69-
}
70-
71-
const { data, error } = await resend.emails.send(emailData)
69+
const emailData: Record<string, unknown> = {
70+
from: validatedData.fromAddress,
71+
to: validatedData.to,
72+
subject: validatedData.subject,
73+
}
74+
75+
if (contentType === 'html') {
76+
emailData.html = validatedData.body
77+
emailData.text = validatedData.body.replace(/<[^>]*>/g, '')
78+
} else {
79+
emailData.text = validatedData.body
80+
}
81+
82+
if (validatedData.cc) {
83+
emailData.cc = validatedData.cc
84+
}
85+
86+
if (validatedData.bcc) {
87+
emailData.bcc = validatedData.bcc
88+
}
89+
90+
if (validatedData.replyTo) {
91+
emailData.replyTo = validatedData.replyTo
92+
}
93+
94+
if (validatedData.scheduledAt) {
95+
emailData.scheduledAt = validatedData.scheduledAt
96+
}
97+
98+
if (validatedData.tags) {
99+
const tagPairs = validatedData.tags.split(',').map((pair) => {
100+
const trimmed = pair.trim()
101+
if (!trimmed.includes(':')) return null
102+
const [name, value] = trimmed.split(':')
103+
return { name: name?.trim() || '', value: value?.trim() || '' }
104+
})
105+
emailData.tags = tagPairs.filter(
106+
(tag): tag is { name: string; value: string } => tag !== null && !!tag.name
107+
)
108+
}
109+
110+
const { data, error } = await resend.emails.send(
111+
emailData as Parameters<typeof resend.emails.send>[0]
112+
)
72113

73114
if (error) {
74115
logger.error(`[${requestId}] Email sending failed:`, error)

0 commit comments

Comments
 (0)