feat(resend): expand integration with contacts, domains, and enhanced email ops (#3366)

This commit is contained in:
Waleed
2026-02-26 22:12:48 -08:00
committed by GitHub
parent f193e9ebbc
commit 47fef540cc
14 changed files with 1069 additions and 44 deletions
+151 -2
View File
@@ -1,6 +1,6 @@
---
title: Resend
description: Send emails with Resend.
description: Send emails and manage contacts with Resend.
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -27,7 +27,7 @@ In Sim, the Resend integration allows your agents to programmatically send email
## Usage Instructions
Integrate Resend into the workflow. Can send emails. Requires API Key.
Integrate Resend into your workflow. Send emails, retrieve email status, manage contacts, and view domains. Requires API Key.
@@ -46,6 +46,11 @@ Send an email using your own Resend API key and from address
| `subject` | string | Yes | Email subject line |
| `body` | string | Yes | Email body content \(plain text or HTML based on contentType\) |
| `contentType` | string | No | Content type for the email body: "text" for plain text or "html" for HTML content |
| `cc` | string | No | Carbon copy recipient email address |
| `bcc` | string | No | Blind carbon copy recipient email address |
| `replyTo` | string | No | Reply-to email address |
| `scheduledAt` | string | No | Schedule email to be sent later in ISO 8601 format |
| `tags` | string | No | Comma-separated key:value pairs for email tags \(e.g., "category:welcome,type:onboarding"\) |
| `resendApiKey` | string | Yes | Resend API key for sending emails |
#### Output
@@ -53,8 +58,152 @@ Send an email using your own Resend API key and from address
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the email was sent successfully |
| `id` | string | Email ID returned by Resend |
| `to` | string | Recipient email address |
| `subject` | string | Email subject |
| `body` | string | Email body content |
### `resend_get_email`
Retrieve details of a previously sent email by its ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `emailId` | string | Yes | The ID of the email to retrieve |
| `resendApiKey` | string | Yes | Resend API key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Email ID |
| `from` | string | Sender email address |
| `to` | json | Recipient email addresses |
| `subject` | string | Email subject |
| `html` | string | HTML email content |
| `text` | string | Plain text email content |
| `cc` | json | CC email addresses |
| `bcc` | json | BCC email addresses |
| `replyTo` | json | Reply-to email addresses |
| `lastEvent` | string | Last event status \(e.g., delivered, bounced\) |
| `createdAt` | string | Email creation timestamp |
| `scheduledAt` | string | Scheduled send timestamp |
| `tags` | json | Email tags as name-value pairs |
### `resend_create_contact`
Create a new contact in Resend
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `email` | string | Yes | Email address of the contact |
| `firstName` | string | No | First name of the contact |
| `lastName` | string | No | Last name of the contact |
| `unsubscribed` | boolean | No | Whether the contact is unsubscribed from all broadcasts |
| `resendApiKey` | string | Yes | Resend API key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Created contact ID |
### `resend_list_contacts`
List all contacts in Resend
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `resendApiKey` | string | Yes | Resend API key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `contacts` | json | Array of contacts with id, email, first_name, last_name, created_at, unsubscribed |
| `hasMore` | boolean | Whether there are more contacts to retrieve |
### `resend_get_contact`
Retrieve details of a contact by ID or email
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `contactId` | string | Yes | The contact ID or email address to retrieve |
| `resendApiKey` | string | Yes | Resend API key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Contact ID |
| `email` | string | Contact email address |
| `firstName` | string | Contact first name |
| `lastName` | string | Contact last name |
| `createdAt` | string | Contact creation timestamp |
| `unsubscribed` | boolean | Whether the contact is unsubscribed |
### `resend_update_contact`
Update an existing contact in Resend
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `contactId` | string | Yes | The contact ID or email address to update |
| `firstName` | string | No | Updated first name |
| `lastName` | string | No | Updated last name |
| `unsubscribed` | boolean | No | Whether the contact should be unsubscribed from all broadcasts |
| `resendApiKey` | string | Yes | Resend API key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Updated contact ID |
### `resend_delete_contact`
Delete a contact from Resend by ID or email
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `contactId` | string | Yes | The contact ID or email address to delete |
| `resendApiKey` | string | Yes | Resend API key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | string | Deleted contact ID |
| `deleted` | boolean | Whether the contact was successfully deleted |
### `resend_list_domains`
List all verified domains in your Resend account
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `resendApiKey` | string | Yes | Resend API key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `domains` | json | Array of domains with id, name, status, region, and createdAt |
| `hasMore` | boolean | Whether there are more domains to retrieve |
+61 -18
View File
@@ -10,12 +10,26 @@ export const dynamic = 'force-dynamic'
const logger = createLogger('MailSendAPI')
const MailSendSchema = z.object({
fromAddress: z.string().email('Invalid from email address').min(1, 'From address is required'),
to: z.string().email('Invalid email address').min(1, 'To email is required'),
fromAddress: z.string().min(1, 'From address is required'),
to: z.string().min(1, 'To email is required'),
subject: z.string().min(1, 'Subject is required'),
body: z.string().min(1, 'Email body is required'),
contentType: z.enum(['text', 'html']).optional().nullable(),
resendApiKey: z.string().min(1, 'Resend API key is required'),
cc: z
.union([z.string().min(1), z.array(z.string().min(1))])
.optional()
.nullable(),
bcc: z
.union([z.string().min(1), z.array(z.string().min(1))])
.optional()
.nullable(),
replyTo: z
.union([z.string().min(1), z.array(z.string().min(1))])
.optional()
.nullable(),
scheduledAt: z.string().datetime().optional().nullable(),
tags: z.string().optional().nullable(),
})
export async function POST(request: NextRequest) {
@@ -52,23 +66,52 @@ export async function POST(request: NextRequest) {
const resend = new Resend(validatedData.resendApiKey)
const contentType = validatedData.contentType || 'text'
const emailData =
contentType === 'html'
? {
from: validatedData.fromAddress,
to: validatedData.to,
subject: validatedData.subject,
html: validatedData.body,
text: validatedData.body.replace(/<[^>]*>/g, ''), // Strip HTML for text version
}
: {
from: validatedData.fromAddress,
to: validatedData.to,
subject: validatedData.subject,
text: validatedData.body,
}
const emailData: Record<string, unknown> = {
from: validatedData.fromAddress,
to: validatedData.to,
subject: validatedData.subject,
}
const { data, error } = await resend.emails.send(emailData)
if (contentType === 'html') {
emailData.html = validatedData.body
emailData.text = validatedData.body.replace(/<[^>]*>/g, '')
} else {
emailData.text = validatedData.body
}
if (validatedData.cc) {
emailData.cc = validatedData.cc
}
if (validatedData.bcc) {
emailData.bcc = validatedData.bcc
}
if (validatedData.replyTo) {
emailData.replyTo = validatedData.replyTo
}
if (validatedData.scheduledAt) {
emailData.scheduledAt = validatedData.scheduledAt
}
if (validatedData.tags) {
const tagPairs = validatedData.tags.split(',').map((pair) => {
const trimmed = pair.trim()
const colonIndex = trimmed.indexOf(':')
if (colonIndex === -1) return null
const name = trimmed.substring(0, colonIndex).trim()
const value = trimmed.substring(colonIndex + 1).trim()
return { name, value: value || '' }
})
emailData.tags = tagPairs.filter(
(tag): tag is { name: string; value: string } => tag !== null && !!tag.name
)
}
const { data, error } = await resend.emails.send(
emailData as unknown as Parameters<typeof resend.emails.send>[0]
)
if (error) {
logger.error(`[${requestId}] Email sending failed:`, error)
+213 -20
View File
@@ -1,23 +1,53 @@
import { ResendIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import type { MailSendResult } from '@/tools/resend/types'
export const ResendBlock: BlockConfig<MailSendResult> = {
export const ResendBlock: BlockConfig = {
type: 'resend',
name: 'Resend',
description: 'Send emails with Resend.',
longDescription: 'Integrate Resend into the workflow. Can send emails. Requires API Key.',
description: 'Send emails and manage contacts with Resend.',
longDescription:
'Integrate Resend into your workflow. Send emails, retrieve email status, manage contacts, and view domains. Requires API Key.',
docsLink: 'https://docs.sim.ai/tools/resend',
category: 'tools',
bgColor: '#181C1E',
icon: ResendIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
// Email Operations
{ label: 'Send Email', id: 'send_email' },
{ label: 'Get Email', id: 'get_email' },
// Contact Operations
{ label: 'Create Contact', id: 'create_contact' },
{ label: 'List Contacts', id: 'list_contacts' },
{ label: 'Get Contact', id: 'get_contact' },
{ label: 'Update Contact', id: 'update_contact' },
{ label: 'Delete Contact', id: 'delete_contact' },
// Domain Operations
{ label: 'List Domains', id: 'list_domains' },
],
value: () => 'send_email',
},
{
id: 'resendApiKey',
title: 'Resend API Key',
type: 'short-input',
placeholder: 'Your Resend API key',
required: true,
password: true,
},
// Send Email fields
{
id: 'fromAddress',
title: 'From Address',
type: 'short-input',
placeholder: 'sender@yourdomain.com',
condition: { field: 'operation', value: 'send_email' },
required: true,
},
{
@@ -25,6 +55,7 @@ export const ResendBlock: BlockConfig<MailSendResult> = {
title: 'To',
type: 'short-input',
placeholder: 'recipient@example.com',
condition: { field: 'operation', value: 'send_email' },
required: true,
},
{
@@ -32,14 +63,46 @@ export const ResendBlock: BlockConfig<MailSendResult> = {
title: 'Subject',
type: 'short-input',
placeholder: 'Email subject',
condition: { field: 'operation', value: 'send_email' },
required: true,
wandConfig: {
enabled: true,
prompt: `Generate a compelling email subject line based on the user's description.
### GUIDELINES
- Keep it concise (50 characters or less is ideal)
- Make it attention-grabbing
- Avoid spam trigger words
- Be clear about the email content
### EXAMPLES
"Welcome email for new users" -> "Welcome to Our Platform!"
"Order confirmation" -> "Your Order #12345 is Confirmed"
"Newsletter about new features" -> "New Features You'll Love"
Return ONLY the subject line - no explanations.`,
placeholder: 'Describe the email topic...',
},
},
{
id: 'body',
title: 'Body',
type: 'long-input',
placeholder: 'Email body content',
condition: { field: 'operation', value: 'send_email' },
required: true,
wandConfig: {
enabled: true,
prompt: `Generate email content based on the user's description.
### GUIDELINES
- Use clear, readable formatting
- Keep paragraphs short
- Include appropriate greeting and sign-off
Return ONLY the email body - no explanations.`,
placeholder: 'Describe the email content...',
},
},
{
id: 'contentType',
@@ -50,45 +113,175 @@ export const ResendBlock: BlockConfig<MailSendResult> = {
{ label: 'HTML', id: 'html' },
],
value: () => 'text',
required: false,
condition: { field: 'operation', value: 'send_email' },
},
{
id: 'resendApiKey',
title: 'Resend API Key',
id: 'cc',
title: 'CC',
type: 'short-input',
placeholder: 'Your Resend API key',
placeholder: 'cc@example.com',
condition: { field: 'operation', value: 'send_email' },
},
{
id: 'bcc',
title: 'BCC',
type: 'short-input',
placeholder: 'bcc@example.com',
condition: { field: 'operation', value: 'send_email' },
},
{
id: 'replyTo',
title: 'Reply To',
type: 'short-input',
placeholder: 'reply@example.com',
condition: { field: 'operation', value: 'send_email' },
},
{
id: 'scheduledAt',
title: 'Schedule At',
type: 'short-input',
placeholder: '2024-08-05T11:52:01.858Z',
condition: { field: 'operation', value: 'send_email' },
},
{
id: 'tags',
title: 'Tags',
type: 'short-input',
placeholder: 'category:welcome,type:onboarding',
condition: { field: 'operation', value: 'send_email' },
},
// Get Email fields
{
id: 'emailId',
title: 'Email ID',
type: 'short-input',
placeholder: 'Email ID to retrieve',
condition: { field: 'operation', value: 'get_email' },
required: true,
},
// Create Contact fields
{
id: 'email',
title: 'Email',
type: 'short-input',
placeholder: 'contact@example.com',
condition: { field: 'operation', value: 'create_contact' },
required: true,
},
{
id: 'firstName',
title: 'First Name',
type: 'short-input',
placeholder: 'John',
condition: { field: 'operation', value: ['create_contact', 'update_contact'] },
},
{
id: 'lastName',
title: 'Last Name',
type: 'short-input',
placeholder: 'Doe',
condition: { field: 'operation', value: ['create_contact', 'update_contact'] },
},
{
id: 'unsubscribed',
title: 'Unsubscribed',
type: 'dropdown',
options: [
{ label: 'No', id: 'false' },
{ label: 'Yes', id: 'true' },
],
value: () => 'false',
condition: { field: 'operation', value: ['create_contact', 'update_contact'] },
},
// Get/Update/Delete Contact fields
{
id: 'contactId',
title: 'Contact ID or Email',
type: 'short-input',
placeholder: 'Contact ID or email address',
condition: { field: 'operation', value: ['get_contact', 'update_contact', 'delete_contact'] },
required: true,
password: true,
},
],
tools: {
access: ['resend_send'],
access: [
'resend_send',
'resend_get_email',
'resend_create_contact',
'resend_list_contacts',
'resend_get_contact',
'resend_update_contact',
'resend_delete_contact',
'resend_list_domains',
],
config: {
tool: () => 'resend_send',
params: (params) => ({
resendApiKey: params.resendApiKey,
fromAddress: params.fromAddress,
to: params.to,
subject: params.subject,
body: params.body,
}),
tool: (params) => {
const operation = params.operation || 'send_email'
if (operation === 'send_email') return 'resend_send'
return `resend_${operation}`
},
params: (params) => {
const { operation, ...rest } = params
if (rest.unsubscribed !== undefined) {
rest.unsubscribed = rest.unsubscribed === 'true'
}
return rest
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
resendApiKey: { type: 'string', description: 'Resend API key' },
// Send email inputs
fromAddress: { type: 'string', description: 'Email address to send from' },
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body content' },
contentType: { type: 'string', description: 'Content type (text or html)' },
resendApiKey: { type: 'string', description: 'Resend API key for sending emails' },
cc: { type: 'string', description: 'CC email address' },
bcc: { type: 'string', description: 'BCC email address' },
replyTo: { type: 'string', description: 'Reply-to email address' },
scheduledAt: { type: 'string', description: 'Scheduled send time in ISO 8601 format' },
tags: { type: 'string', description: 'Email tags as key:value pairs' },
// Get email inputs
emailId: { type: 'string', description: 'Email ID to retrieve' },
// Contact inputs
email: { type: 'string', description: 'Contact email address' },
firstName: { type: 'string', description: 'Contact first name' },
lastName: { type: 'string', description: 'Contact last name' },
unsubscribed: { type: 'string', description: 'Contact subscription status' },
contactId: { type: 'string', description: 'Contact ID or email address' },
},
outputs: {
success: { type: 'boolean', description: 'Whether the email was sent successfully' },
success: { type: 'boolean', description: 'Operation success status' },
// Send email outputs
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body content' },
// Get email outputs
id: { type: 'string', description: 'Email or contact ID' },
from: { type: 'string', description: 'Sender email address' },
html: { type: 'string', description: 'HTML email content' },
text: { type: 'string', description: 'Plain text email content' },
lastEvent: { type: 'string', description: 'Last event status' },
createdAt: { type: 'string', description: 'Creation timestamp' },
tags: { type: 'json', description: 'Email tags as name-value pairs' },
// Contact outputs
email: { type: 'string', description: 'Contact email address' },
firstName: { type: 'string', description: 'Contact first name' },
lastName: { type: 'string', description: 'Contact last name' },
contacts: { type: 'json', description: 'Array of contacts' },
// Domain outputs
domains: { type: 'json', description: 'Array of domains' },
hasMore: { type: 'boolean', description: 'Whether more results are available' },
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}
+17 -1
View File
@@ -1590,7 +1590,16 @@ import {
redisTtlTool,
} from '@/tools/redis'
import { reductoParserTool, reductoParserV2Tool } from '@/tools/reducto'
import { mailSendTool } from '@/tools/resend'
import {
mailSendTool,
resendCreateContactTool,
resendDeleteContactTool,
resendGetContactTool,
resendGetEmailTool,
resendListContactsTool,
resendListDomainsTool,
resendUpdateContactTool,
} from '@/tools/resend'
import {
revenuecatCreatePurchaseTool,
revenuecatDeferGoogleSubscriptionTool,
@@ -2341,6 +2350,13 @@ export const tools: Record<string, ToolConfig> = {
linkedin_share_post: linkedInSharePostTool,
linkedin_get_profile: linkedInGetProfileTool,
resend_send: mailSendTool,
resend_get_email: resendGetEmailTool,
resend_create_contact: resendCreateContactTool,
resend_list_contacts: resendListContactsTool,
resend_get_contact: resendGetContactTool,
resend_update_contact: resendUpdateContactTool,
resend_delete_contact: resendDeleteContactTool,
resend_list_domains: resendListDomainsTool,
sendgrid_send_mail: sendGridSendMailTool,
sendgrid_add_contact: sendGridAddContactTool,
sendgrid_get_contact: sendGridGetContactTool,
+72
View File
@@ -0,0 +1,72 @@
import type { CreateContactParams, CreateContactResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
export const resendCreateContactTool: ToolConfig<CreateContactParams, CreateContactResult> = {
id: 'resend_create_contact',
name: 'Create Contact',
description: 'Create a new contact in Resend',
version: '1.0.0',
params: {
email: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email address of the contact',
},
firstName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'First name of the contact',
},
lastName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Last name of the contact',
},
unsubscribed: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the contact is unsubscribed from all broadcasts',
},
resendApiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Resend API key',
},
},
request: {
url: 'https://api.resend.com/contacts',
method: 'POST',
headers: (params: CreateContactParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
'Content-Type': 'application/json',
}),
body: (params: CreateContactParams) => ({
email: params.email,
...(params.firstName && { first_name: params.firstName }),
...(params.lastName && { last_name: params.lastName }),
...(params.unsubscribed !== undefined && { unsubscribed: params.unsubscribed }),
}),
},
transformResponse: async (response: Response): Promise<CreateContactResult> => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
},
}
},
outputs: {
id: { type: 'string', description: 'Created contact ID' },
},
}
+51
View File
@@ -0,0 +1,51 @@
import type { DeleteContactParams, DeleteContactResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
export const resendDeleteContactTool: ToolConfig<DeleteContactParams, DeleteContactResult> = {
id: 'resend_delete_contact',
name: 'Delete Contact',
description: 'Delete a contact from Resend by ID or email',
version: '1.0.0',
params: {
contactId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The contact ID or email address to delete',
},
resendApiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Resend API key',
},
},
request: {
url: (params: DeleteContactParams) =>
`https://api.resend.com/contacts/${encodeURIComponent(params.contactId)}`,
method: 'DELETE',
headers: (params: DeleteContactParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<DeleteContactResult> => {
const data = await response.json()
return {
success: true,
output: {
id: data.contact || data.id || '',
deleted: data.deleted ?? true,
},
}
},
outputs: {
id: { type: 'string', description: 'Deleted contact ID' },
deleted: { type: 'boolean', description: 'Whether the contact was successfully deleted' },
},
}
+59
View File
@@ -0,0 +1,59 @@
import type { GetContactParams, GetContactResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
export const resendGetContactTool: ToolConfig<GetContactParams, GetContactResult> = {
id: 'resend_get_contact',
name: 'Get Contact',
description: 'Retrieve details of a contact by ID or email',
version: '1.0.0',
params: {
contactId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The contact ID or email address to retrieve',
},
resendApiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Resend API key',
},
},
request: {
url: (params: GetContactParams) =>
`https://api.resend.com/contacts/${encodeURIComponent(params.contactId)}`,
method: 'GET',
headers: (params: GetContactParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GetContactResult> => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
email: data.email,
firstName: data.first_name || '',
lastName: data.last_name || '',
createdAt: data.created_at || '',
unsubscribed: data.unsubscribed || false,
},
}
},
outputs: {
id: { type: 'string', description: 'Contact ID' },
email: { type: 'string', description: 'Contact email address' },
firstName: { type: 'string', description: 'Contact first name' },
lastName: { type: 'string', description: 'Contact last name' },
createdAt: { type: 'string', description: 'Contact creation timestamp' },
unsubscribed: { type: 'boolean', description: 'Whether the contact is unsubscribed' },
},
}
+72
View File
@@ -0,0 +1,72 @@
import type { GetEmailParams, GetEmailResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
export const resendGetEmailTool: ToolConfig<GetEmailParams, GetEmailResult> = {
id: 'resend_get_email',
name: 'Get Email',
description: 'Retrieve details of a previously sent email by its ID',
version: '1.0.0',
params: {
emailId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the email to retrieve',
},
resendApiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Resend API key',
},
},
request: {
url: (params: GetEmailParams) => `https://api.resend.com/emails/${params.emailId}`,
method: 'GET',
headers: (params: GetEmailParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<GetEmailResult> => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
from: data.from,
to: data.to || [],
subject: data.subject,
html: data.html || '',
text: data.text || null,
cc: data.cc || [],
bcc: data.bcc || [],
replyTo: data.reply_to || [],
lastEvent: data.last_event || '',
createdAt: data.created_at || '',
scheduledAt: data.scheduled_at || null,
tags: data.tags || [],
},
}
},
outputs: {
id: { type: 'string', description: 'Email ID' },
from: { type: 'string', description: 'Sender email address' },
to: { type: 'json', description: 'Recipient email addresses' },
subject: { type: 'string', description: 'Email subject' },
html: { type: 'string', description: 'HTML email content' },
text: { type: 'string', description: 'Plain text email content' },
cc: { type: 'json', description: 'CC email addresses' },
bcc: { type: 'json', description: 'BCC email addresses' },
replyTo: { type: 'json', description: 'Reply-to email addresses' },
lastEvent: { type: 'string', description: 'Last event status (e.g., delivered, bounced)' },
createdAt: { type: 'string', description: 'Email creation timestamp' },
scheduledAt: { type: 'string', description: 'Scheduled send timestamp' },
tags: { type: 'json', description: 'Email tags as name-value pairs' },
},
}
+26 -3
View File
@@ -1,3 +1,26 @@
import { mailSendTool } from '@/tools/resend/send'
export { mailSendTool }
export { resendCreateContactTool } from '@/tools/resend/create_contact'
export { resendDeleteContactTool } from '@/tools/resend/delete_contact'
export { resendGetContactTool } from '@/tools/resend/get_contact'
export { resendGetEmailTool } from '@/tools/resend/get_email'
export { resendListContactsTool } from '@/tools/resend/list_contacts'
export { resendListDomainsTool } from '@/tools/resend/list_domains'
export { mailSendTool } from '@/tools/resend/send'
export type {
CreateContactParams,
CreateContactResult,
DeleteContactParams,
DeleteContactResult,
GetContactParams,
GetContactResult,
GetEmailParams,
GetEmailResult,
ListContactsParams,
ListContactsResult,
ListDomainsParams,
ListDomainsResult,
MailSendParams,
MailSendResult,
UpdateContactParams,
UpdateContactResult,
} from '@/tools/resend/types'
export { resendUpdateContactTool } from '@/tools/resend/update_contact'
+48
View File
@@ -0,0 +1,48 @@
import type { ListContactsParams, ListContactsResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
export const resendListContactsTool: ToolConfig<ListContactsParams, ListContactsResult> = {
id: 'resend_list_contacts',
name: 'List Contacts',
description: 'List all contacts in Resend',
version: '1.0.0',
params: {
resendApiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Resend API key',
},
},
request: {
url: 'https://api.resend.com/contacts',
method: 'GET',
headers: (params: ListContactsParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<ListContactsResult> => {
const data = await response.json()
return {
success: true,
output: {
contacts: data.data || [],
hasMore: data.has_more || false,
},
}
},
outputs: {
contacts: {
type: 'json',
description:
'Array of contacts with id, email, first_name, last_name, created_at, unsubscribed',
},
hasMore: { type: 'boolean', description: 'Whether there are more contacts to retrieve' },
},
}
+61
View File
@@ -0,0 +1,61 @@
import type { ListDomainsParams, ListDomainsResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
export const resendListDomainsTool: ToolConfig<ListDomainsParams, ListDomainsResult> = {
id: 'resend_list_domains',
name: 'List Domains',
description: 'List all verified domains in your Resend account',
version: '1.0.0',
params: {
resendApiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Resend API key',
},
},
request: {
url: 'https://api.resend.com/domains',
method: 'GET',
headers: (params: ListDomainsParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response): Promise<ListDomainsResult> => {
const data = await response.json()
return {
success: true,
output: {
domains: (data.data || []).map(
(domain: {
id: string
name: string
status: string
region: string
created_at: string
}) => ({
id: domain.id,
name: domain.name,
status: domain.status,
region: domain.region,
createdAt: domain.created_at,
})
),
hasMore: data.has_more || false,
},
}
},
outputs: {
domains: {
type: 'json',
description: 'Array of domains with id, name, status, region, and createdAt',
},
hasMore: { type: 'boolean', description: 'Whether there are more domains to retrieve' },
},
}
+38
View File
@@ -41,6 +41,37 @@ export const mailSendTool: ToolConfig<MailSendParams, MailSendResult> = {
description:
'Content type for the email body: "text" for plain text or "html" for HTML content',
},
cc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Carbon copy recipient email address',
},
bcc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Blind carbon copy recipient email address',
},
replyTo: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Reply-to email address',
},
scheduledAt: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Schedule email to be sent later in ISO 8601 format',
},
tags: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Comma-separated key:value pairs for email tags (e.g., "category:welcome,type:onboarding")',
},
resendApiKey: {
type: 'string',
required: true,
@@ -62,6 +93,11 @@ export const mailSendTool: ToolConfig<MailSendParams, MailSendResult> = {
subject: params.subject,
body: params.body,
contentType: params.contentType || 'text',
...(params.cc && { cc: params.cc }),
...(params.bcc && { bcc: params.bcc }),
...(params.replyTo && { replyTo: params.replyTo }),
...(params.scheduledAt && { scheduledAt: params.scheduledAt }),
...(params.tags && { tags: params.tags }),
}),
},
@@ -72,6 +108,7 @@ export const mailSendTool: ToolConfig<MailSendParams, MailSendResult> = {
success: true,
output: {
success: result.success,
id: result.data?.id || '',
to: params?.to || '',
subject: params?.subject || '',
body: params?.body || '',
@@ -81,6 +118,7 @@ export const mailSendTool: ToolConfig<MailSendParams, MailSendResult> = {
outputs: {
success: { type: 'boolean', description: 'Whether the email was sent successfully' },
id: { type: 'string', description: 'Email ID returned by Resend' },
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body content' },
+128
View File
@@ -1,5 +1,6 @@
import type { ToolResponse } from '@/tools/types'
/** Send Email */
export interface MailSendParams {
resendApiKey: string
fromAddress: string
@@ -7,13 +8,140 @@ export interface MailSendParams {
subject: string
body: string
contentType?: 'text' | 'html'
cc?: string
bcc?: string
replyTo?: string
scheduledAt?: string
tags?: string
}
export interface MailSendResult extends ToolResponse {
output: {
success: boolean
id: string
to: string
subject: string
body: string
}
}
/** Get Email */
export interface GetEmailParams {
resendApiKey: string
emailId: string
}
export interface GetEmailResult extends ToolResponse {
output: {
id: string
from: string
to: string[]
subject: string
html: string
text: string | null
cc: string[]
bcc: string[]
replyTo: string[]
lastEvent: string
createdAt: string
scheduledAt: string | null
tags: Array<{ name: string; value: string }>
}
}
/** Create Contact */
export interface CreateContactParams {
resendApiKey: string
email: string
firstName?: string
lastName?: string
unsubscribed?: boolean
}
export interface CreateContactResult extends ToolResponse {
output: {
id: string
}
}
/** List Contacts */
export interface ListContactsParams {
resendApiKey: string
}
export interface ListContactsResult extends ToolResponse {
output: {
contacts: Array<{
id: string
email: string
first_name: string
last_name: string
created_at: string
unsubscribed: boolean
}>
hasMore: boolean
}
}
/** Get Contact */
export interface GetContactParams {
resendApiKey: string
contactId: string
}
export interface GetContactResult extends ToolResponse {
output: {
id: string
email: string
firstName: string
lastName: string
createdAt: string
unsubscribed: boolean
}
}
/** Update Contact */
export interface UpdateContactParams {
resendApiKey: string
contactId: string
firstName?: string
lastName?: string
unsubscribed?: boolean
}
export interface UpdateContactResult extends ToolResponse {
output: {
id: string
}
}
/** Delete Contact */
export interface DeleteContactParams {
resendApiKey: string
contactId: string
}
export interface DeleteContactResult extends ToolResponse {
output: {
id: string
deleted: boolean
}
}
/** List Domains */
export interface ListDomainsParams {
resendApiKey: string
}
export interface ListDomainsResult extends ToolResponse {
output: {
domains: Array<{
id: string
name: string
status: string
region: string
createdAt: string
}>
hasMore: boolean
}
}
+72
View File
@@ -0,0 +1,72 @@
import type { UpdateContactParams, UpdateContactResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
export const resendUpdateContactTool: ToolConfig<UpdateContactParams, UpdateContactResult> = {
id: 'resend_update_contact',
name: 'Update Contact',
description: 'Update an existing contact in Resend',
version: '1.0.0',
params: {
contactId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The contact ID or email address to update',
},
firstName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated first name',
},
lastName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated last name',
},
unsubscribed: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the contact should be unsubscribed from all broadcasts',
},
resendApiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Resend API key',
},
},
request: {
url: (params: UpdateContactParams) =>
`https://api.resend.com/contacts/${encodeURIComponent(params.contactId)}`,
method: 'PATCH',
headers: (params: UpdateContactParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
'Content-Type': 'application/json',
}),
body: (params: UpdateContactParams) => ({
...(params.firstName !== undefined && { first_name: params.firstName }),
...(params.lastName !== undefined && { last_name: params.lastName }),
...(params.unsubscribed !== undefined && { unsubscribed: params.unsubscribed }),
}),
},
transformResponse: async (response: Response): Promise<UpdateContactResult> => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
},
}
},
outputs: {
id: { type: 'string', description: 'Updated contact ID' },
},
}