Skip to content

Commit 47fef54

Browse files
authored
feat(resend): expand integration with contacts, domains, and enhanced email ops (#3366)
1 parent f193e9e commit 47fef54

File tree

14 files changed

+1070
-45
lines changed

14 files changed

+1070
-45
lines changed

apps/docs/content/docs/en/tools/resend.mdx

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Resend
3-
description: Send emails with Resend.
3+
description: Send emails and manage contacts with Resend.
44
---
55

66
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -27,7 +27,7 @@ In Sim, the Resend integration allows your agents to programmatically send email
2727

2828
## Usage Instructions
2929

30-
Integrate Resend into the workflow. Can send emails. Requires API Key.
30+
Integrate Resend into your workflow. Send emails, retrieve email status, manage contacts, and view domains. Requires API Key.
3131

3232

3333

@@ -46,15 +46,164 @@ Send an email using your own Resend API key and from address
4646
| `subject` | string | Yes | Email subject line |
4747
| `body` | string | Yes | Email body content \(plain text or HTML based on contentType\) |
4848
| `contentType` | string | No | Content type for the email body: "text" for plain text or "html" for HTML content |
49+
| `cc` | string | No | Carbon copy recipient email address |
50+
| `bcc` | string | No | Blind carbon copy recipient email address |
51+
| `replyTo` | string | No | Reply-to email address |
52+
| `scheduledAt` | string | No | Schedule email to be sent later in ISO 8601 format |
53+
| `tags` | string | No | Comma-separated key:value pairs for email tags \(e.g., "category:welcome,type:onboarding"\) |
4954
| `resendApiKey` | string | Yes | Resend API key for sending emails |
5055

5156
#### Output
5257

5358
| Parameter | Type | Description |
5459
| --------- | ---- | ----------- |
5560
| `success` | boolean | Whether the email was sent successfully |
61+
| `id` | string | Email ID returned by Resend |
5662
| `to` | string | Recipient email address |
5763
| `subject` | string | Email subject |
5864
| `body` | string | Email body content |
5965

66+
### `resend_get_email`
67+
68+
Retrieve details of a previously sent email by its ID
69+
70+
#### Input
71+
72+
| Parameter | Type | Required | Description |
73+
| --------- | ---- | -------- | ----------- |
74+
| `emailId` | string | Yes | The ID of the email to retrieve |
75+
| `resendApiKey` | string | Yes | Resend API key |
76+
77+
#### Output
78+
79+
| Parameter | Type | Description |
80+
| --------- | ---- | ----------- |
81+
| `id` | string | Email ID |
82+
| `from` | string | Sender email address |
83+
| `to` | json | Recipient email addresses |
84+
| `subject` | string | Email subject |
85+
| `html` | string | HTML email content |
86+
| `text` | string | Plain text email content |
87+
| `cc` | json | CC email addresses |
88+
| `bcc` | json | BCC email addresses |
89+
| `replyTo` | json | Reply-to email addresses |
90+
| `lastEvent` | string | Last event status \(e.g., delivered, bounced\) |
91+
| `createdAt` | string | Email creation timestamp |
92+
| `scheduledAt` | string | Scheduled send timestamp |
93+
| `tags` | json | Email tags as name-value pairs |
94+
95+
### `resend_create_contact`
96+
97+
Create a new contact in Resend
98+
99+
#### Input
100+
101+
| Parameter | Type | Required | Description |
102+
| --------- | ---- | -------- | ----------- |
103+
| `email` | string | Yes | Email address of the contact |
104+
| `firstName` | string | No | First name of the contact |
105+
| `lastName` | string | No | Last name of the contact |
106+
| `unsubscribed` | boolean | No | Whether the contact is unsubscribed from all broadcasts |
107+
| `resendApiKey` | string | Yes | Resend API key |
108+
109+
#### Output
110+
111+
| Parameter | Type | Description |
112+
| --------- | ---- | ----------- |
113+
| `id` | string | Created contact ID |
114+
115+
### `resend_list_contacts`
116+
117+
List all contacts in Resend
118+
119+
#### Input
120+
121+
| Parameter | Type | Required | Description |
122+
| --------- | ---- | -------- | ----------- |
123+
| `resendApiKey` | string | Yes | Resend API key |
124+
125+
#### Output
126+
127+
| Parameter | Type | Description |
128+
| --------- | ---- | ----------- |
129+
| `contacts` | json | Array of contacts with id, email, first_name, last_name, created_at, unsubscribed |
130+
| `hasMore` | boolean | Whether there are more contacts to retrieve |
131+
132+
### `resend_get_contact`
133+
134+
Retrieve details of a contact by ID or email
135+
136+
#### Input
137+
138+
| Parameter | Type | Required | Description |
139+
| --------- | ---- | -------- | ----------- |
140+
| `contactId` | string | Yes | The contact ID or email address to retrieve |
141+
| `resendApiKey` | string | Yes | Resend API key |
142+
143+
#### Output
144+
145+
| Parameter | Type | Description |
146+
| --------- | ---- | ----------- |
147+
| `id` | string | Contact ID |
148+
| `email` | string | Contact email address |
149+
| `firstName` | string | Contact first name |
150+
| `lastName` | string | Contact last name |
151+
| `createdAt` | string | Contact creation timestamp |
152+
| `unsubscribed` | boolean | Whether the contact is unsubscribed |
153+
154+
### `resend_update_contact`
155+
156+
Update an existing contact in Resend
157+
158+
#### Input
159+
160+
| Parameter | Type | Required | Description |
161+
| --------- | ---- | -------- | ----------- |
162+
| `contactId` | string | Yes | The contact ID or email address to update |
163+
| `firstName` | string | No | Updated first name |
164+
| `lastName` | string | No | Updated last name |
165+
| `unsubscribed` | boolean | No | Whether the contact should be unsubscribed from all broadcasts |
166+
| `resendApiKey` | string | Yes | Resend API key |
167+
168+
#### Output
169+
170+
| Parameter | Type | Description |
171+
| --------- | ---- | ----------- |
172+
| `id` | string | Updated contact ID |
173+
174+
### `resend_delete_contact`
175+
176+
Delete a contact from Resend by ID or email
177+
178+
#### Input
179+
180+
| Parameter | Type | Required | Description |
181+
| --------- | ---- | -------- | ----------- |
182+
| `contactId` | string | Yes | The contact ID or email address to delete |
183+
| `resendApiKey` | string | Yes | Resend API key |
184+
185+
#### Output
186+
187+
| Parameter | Type | Description |
188+
| --------- | ---- | ----------- |
189+
| `id` | string | Deleted contact ID |
190+
| `deleted` | boolean | Whether the contact was successfully deleted |
191+
192+
### `resend_list_domains`
193+
194+
List all verified domains in your Resend account
195+
196+
#### Input
197+
198+
| Parameter | Type | Required | Description |
199+
| --------- | ---- | -------- | ----------- |
200+
| `resendApiKey` | string | Yes | Resend API key |
201+
202+
#### Output
203+
204+
| Parameter | Type | Description |
205+
| --------- | ---- | ----------- |
206+
| `domains` | json | Array of domains with id, name, status, region, and createdAt |
207+
| `hasMore` | boolean | Whether there are more domains to retrieve |
208+
60209

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

Lines changed: 62 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().min(1), z.array(z.string().min(1))])
21+
.optional()
22+
.nullable(),
23+
bcc: z
24+
.union([z.string().min(1), z.array(z.string().min(1))])
25+
.optional()
26+
.nullable(),
27+
replyTo: z
28+
.union([z.string().min(1), z.array(z.string().min(1))])
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,52 @@ 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+
const colonIndex = trimmed.indexOf(':')
102+
if (colonIndex === -1) return null
103+
const name = trimmed.substring(0, colonIndex).trim()
104+
const value = trimmed.substring(colonIndex + 1).trim()
105+
return { name, value: value || '' }
106+
})
107+
emailData.tags = tagPairs.filter(
108+
(tag): tag is { name: string; value: string } => tag !== null && !!tag.name
109+
)
110+
}
111+
112+
const { data, error } = await resend.emails.send(
113+
emailData as unknown as Parameters<typeof resend.emails.send>[0]
114+
)
72115

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

0 commit comments

Comments
 (0)