improvement(resend): add error handling, authMode, and naming consistency (#3382)

This commit is contained in:
Waleed
2026-02-28 11:19:42 -08:00
committed by GitHub
parent 647a3eb05b
commit 96096e0ad1
11 changed files with 176 additions and 49 deletions
+26 -17
View File
@@ -1,5 +1,6 @@
import { ResendIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
export const ResendBlock: BlockConfig = {
type: 'resend',
@@ -11,6 +12,7 @@ export const ResendBlock: BlockConfig = {
category: 'tools',
bgColor: '#181C1E',
icon: ResendIcon,
authMode: AuthMode.ApiKey,
subBlocks: [
{
@@ -18,16 +20,13 @@ export const ResendBlock: BlockConfig = {
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',
@@ -41,7 +40,6 @@ export const ResendBlock: BlockConfig = {
password: true,
},
// Send Email fields
{
id: 'fromAddress',
title: 'From Address',
@@ -80,7 +78,7 @@ export const ResendBlock: BlockConfig = {
"Order confirmation" -> "Your Order #12345 is Confirmed"
"Newsletter about new features" -> "New Features You'll Love"
Return ONLY the subject line - no explanations.`,
Return ONLY the subject line - no explanations, no extra text.`,
placeholder: 'Describe the email topic...',
},
},
@@ -100,7 +98,7 @@ Return ONLY the subject line - no explanations.`,
- Keep paragraphs short
- Include appropriate greeting and sign-off
Return ONLY the email body - no explanations.`,
Return ONLY the email body - no explanations, no extra text.`,
placeholder: 'Describe the email content...',
},
},
@@ -114,6 +112,7 @@ Return ONLY the email body - no explanations.`,
],
value: () => 'text',
condition: { field: 'operation', value: 'send_email' },
mode: 'advanced',
},
{
id: 'cc',
@@ -121,6 +120,7 @@ Return ONLY the email body - no explanations.`,
type: 'short-input',
placeholder: 'cc@example.com',
condition: { field: 'operation', value: 'send_email' },
mode: 'advanced',
},
{
id: 'bcc',
@@ -128,6 +128,7 @@ Return ONLY the email body - no explanations.`,
type: 'short-input',
placeholder: 'bcc@example.com',
condition: { field: 'operation', value: 'send_email' },
mode: 'advanced',
},
{
id: 'replyTo',
@@ -135,6 +136,7 @@ Return ONLY the email body - no explanations.`,
type: 'short-input',
placeholder: 'reply@example.com',
condition: { field: 'operation', value: 'send_email' },
mode: 'advanced',
},
{
id: 'scheduledAt',
@@ -142,6 +144,14 @@ Return ONLY the email body - no explanations.`,
type: 'short-input',
placeholder: '2024-08-05T11:52:01.858Z',
condition: { field: 'operation', value: 'send_email' },
mode: 'advanced',
wandConfig: {
enabled: true,
generationType: 'timestamp',
prompt:
'Generate an ISO 8601 timestamp for scheduling email delivery. Return ONLY the timestamp - no explanations, no extra text.',
placeholder: 'Describe when to send (e.g., "tomorrow at 9am")...',
},
},
{
id: 'tags',
@@ -149,9 +159,15 @@ Return ONLY the email body - no explanations.`,
type: 'short-input',
placeholder: 'category:welcome,type:onboarding',
condition: { field: 'operation', value: 'send_email' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt:
'Generate comma-separated key:value pairs for email tags based on the description. Example format: "category:welcome,type:onboarding". Return ONLY the tag pairs - no explanations, no extra text.',
placeholder: 'Describe the email tags...',
},
},
// Get Email fields
{
id: 'emailId',
title: 'Email ID',
@@ -161,7 +177,6 @@ Return ONLY the email body - no explanations.`,
required: true,
},
// Create Contact fields
{
id: 'email',
title: 'Email',
@@ -196,7 +211,6 @@ Return ONLY the email body - no explanations.`,
condition: { field: 'operation', value: ['create_contact', 'update_contact'] },
},
// Get/Update/Delete Contact fields
{
id: 'contactId',
title: 'Contact ID or Email',
@@ -239,7 +253,6 @@ Return ONLY the email body - no explanations.`,
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' },
@@ -250,9 +263,7 @@ Return ONLY the email body - no explanations.`,
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' },
@@ -262,24 +273,22 @@ Return ONLY the email body - no explanations.`,
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
// Send email outputs
id: { type: 'string', description: 'Email or contact ID' },
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' },
scheduledAt: { type: 'string', description: 'Scheduled send 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' },
unsubscribed: { type: 'boolean', description: 'Whether the contact is unsubscribed' },
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' },
+2 -2
View File
@@ -1599,13 +1599,13 @@ import {
} from '@/tools/redis'
import { reductoParserTool, reductoParserV2Tool } from '@/tools/reducto'
import {
mailSendTool,
resendCreateContactTool,
resendDeleteContactTool,
resendGetContactTool,
resendGetEmailTool,
resendListContactsTool,
resendListDomainsTool,
resendSendTool,
resendUpdateContactTool,
} from '@/tools/resend'
import {
@@ -2390,7 +2390,7 @@ export const tools: Record<string, ToolConfig> = {
luma_update_event: lumaUpdateEventTool,
linkedin_share_post: linkedInSharePostTool,
linkedin_get_profile: linkedInGetProfileTool,
resend_send: mailSendTool,
resend_send: resendSendTool,
resend_get_email: resendGetEmailTool,
resend_create_contact: resendCreateContactTool,
resend_list_contacts: resendListContactsTool,
+14
View File
@@ -1,6 +1,9 @@
import { createLogger } from '@sim/logger'
import type { CreateContactParams, CreateContactResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ResendCreateContactTool')
export const resendCreateContactTool: ToolConfig<CreateContactParams, CreateContactResult> = {
id: 'resend_create_contact',
name: 'Create Contact',
@@ -58,6 +61,17 @@ export const resendCreateContactTool: ToolConfig<CreateContactParams, CreateCont
transformResponse: async (response: Response): Promise<CreateContactResult> => {
const data = await response.json()
if (!data.id) {
logger.error('Resend Create Contact API error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.message || 'Failed to create contact',
output: {
id: '',
},
}
}
return {
success: true,
output: {
+17 -2
View File
@@ -1,6 +1,9 @@
import { createLogger } from '@sim/logger'
import type { DeleteContactParams, DeleteContactResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ResendDeleteContactTool')
export const resendDeleteContactTool: ToolConfig<DeleteContactParams, DeleteContactResult> = {
id: 'resend_delete_contact',
name: 'Delete Contact',
@@ -24,7 +27,7 @@ export const resendDeleteContactTool: ToolConfig<DeleteContactParams, DeleteCont
request: {
url: (params: DeleteContactParams) =>
`https://api.resend.com/contacts/${encodeURIComponent(params.contactId)}`,
`https://api.resend.com/contacts/${encodeURIComponent(params.contactId.trim())}`,
method: 'DELETE',
headers: (params: DeleteContactParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
@@ -35,10 +38,22 @@ export const resendDeleteContactTool: ToolConfig<DeleteContactParams, DeleteCont
transformResponse: async (response: Response): Promise<DeleteContactResult> => {
const data = await response.json()
if (data.message && !data.deleted) {
logger.error('Resend Delete Contact API error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.message || 'Failed to delete contact',
output: {
id: '',
deleted: false,
},
}
}
return {
success: true,
output: {
id: data.contact || data.id || '',
id: data.contact ?? data.id ?? '',
deleted: data.deleted ?? true,
},
}
+25 -6
View File
@@ -1,6 +1,9 @@
import { createLogger } from '@sim/logger'
import type { GetContactParams, GetContactResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ResendGetContactTool')
export const resendGetContactTool: ToolConfig<GetContactParams, GetContactResult> = {
id: 'resend_get_contact',
name: 'Get Contact',
@@ -24,7 +27,7 @@ export const resendGetContactTool: ToolConfig<GetContactParams, GetContactResult
request: {
url: (params: GetContactParams) =>
`https://api.resend.com/contacts/${encodeURIComponent(params.contactId)}`,
`https://api.resend.com/contacts/${encodeURIComponent(params.contactId.trim())}`,
method: 'GET',
headers: (params: GetContactParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
@@ -35,15 +38,31 @@ export const resendGetContactTool: ToolConfig<GetContactParams, GetContactResult
transformResponse: async (response: Response): Promise<GetContactResult> => {
const data = await response.json()
if (!data.id) {
logger.error('Resend Get Contact API error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.message || 'Failed to retrieve contact',
output: {
id: '',
email: '',
firstName: '',
lastName: '',
createdAt: '',
unsubscribed: false,
},
}
}
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,
email: data.email ?? '',
firstName: data.first_name ?? '',
lastName: data.last_name ?? '',
createdAt: data.created_at ?? '',
unsubscribed: data.unsubscribed ?? false,
},
}
},
+41 -15
View File
@@ -1,6 +1,9 @@
import { createLogger } from '@sim/logger'
import type { GetEmailParams, GetEmailResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ResendGetEmailTool')
export const resendGetEmailTool: ToolConfig<GetEmailParams, GetEmailResult> = {
id: 'resend_get_email',
name: 'Get Email',
@@ -23,7 +26,7 @@ export const resendGetEmailTool: ToolConfig<GetEmailParams, GetEmailResult> = {
},
request: {
url: (params: GetEmailParams) => `https://api.resend.com/emails/${params.emailId}`,
url: (params: GetEmailParams) => `https://api.resend.com/emails/${params.emailId.trim()}`,
method: 'GET',
headers: (params: GetEmailParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
@@ -34,22 +37,45 @@ export const resendGetEmailTool: ToolConfig<GetEmailParams, GetEmailResult> = {
transformResponse: async (response: Response): Promise<GetEmailResult> => {
const data = await response.json()
if (!data.id) {
logger.error('Resend Get Email API error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.message || 'Failed to retrieve email',
output: {
id: '',
from: '',
to: [],
subject: '',
html: '',
text: null,
cc: [],
bcc: [],
replyTo: [],
lastEvent: '',
createdAt: '',
scheduledAt: null,
tags: [],
},
}
}
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 || [],
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 ?? [],
},
}
},
@@ -60,13 +86,13 @@ export const resendGetEmailTool: ToolConfig<GetEmailParams, GetEmailResult> = {
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' },
text: { type: 'string', description: 'Plain text email content', optional: true },
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' },
scheduledAt: { type: 'string', description: 'Scheduled send timestamp', optional: true },
tags: { type: 'json', description: 'Email tags as name-value pairs' },
},
}
+1 -1
View File
@@ -4,7 +4,7 @@ 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 { resendSendTool } from '@/tools/resend/send'
export type {
CreateContactParams,
CreateContactResult,
+17 -2
View File
@@ -1,6 +1,9 @@
import { createLogger } from '@sim/logger'
import type { ListContactsParams, ListContactsResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ResendListContactsTool')
export const resendListContactsTool: ToolConfig<ListContactsParams, ListContactsResult> = {
id: 'resend_list_contacts',
name: 'List Contacts',
@@ -28,11 +31,23 @@ export const resendListContactsTool: ToolConfig<ListContactsParams, ListContacts
transformResponse: async (response: Response): Promise<ListContactsResult> => {
const data = await response.json()
if (data.message) {
logger.error('Resend List Contacts API error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.message || 'Failed to list contacts',
output: {
contacts: [],
hasMore: false,
},
}
}
return {
success: true,
output: {
contacts: data.data || [],
hasMore: data.has_more || false,
contacts: data.data ?? [],
hasMore: data.has_more ?? false,
},
}
},
+17 -2
View File
@@ -1,6 +1,9 @@
import { createLogger } from '@sim/logger'
import type { ListDomainsParams, ListDomainsResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ResendListDomainsTool')
export const resendListDomainsTool: ToolConfig<ListDomainsParams, ListDomainsResult> = {
id: 'resend_list_domains',
name: 'List Domains',
@@ -28,10 +31,22 @@ export const resendListDomainsTool: ToolConfig<ListDomainsParams, ListDomainsRes
transformResponse: async (response: Response): Promise<ListDomainsResult> => {
const data = await response.json()
if (data.message) {
logger.error('Resend List Domains API error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.message || 'Failed to list domains',
output: {
domains: [],
hasMore: false,
},
}
}
return {
success: true,
output: {
domains: (data.data || []).map(
domains: (data.data ?? []).map(
(domain: {
id: string
name: string
@@ -46,7 +61,7 @@ export const resendListDomainsTool: ToolConfig<ListDomainsParams, ListDomainsRes
createdAt: domain.created_at,
})
),
hasMore: data.has_more || false,
hasMore: data.has_more ?? false,
},
}
},
+1 -1
View File
@@ -1,7 +1,7 @@
import type { MailSendParams, MailSendResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
export const mailSendTool: ToolConfig<MailSendParams, MailSendResult> = {
export const resendSendTool: ToolConfig<MailSendParams, MailSendResult> = {
id: 'resend_send',
name: 'Send Email',
description: 'Send an email using your own Resend API key and from address',
+15 -1
View File
@@ -1,6 +1,9 @@
import { createLogger } from '@sim/logger'
import type { UpdateContactParams, UpdateContactResult } from '@/tools/resend/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ResendUpdateContactTool')
export const resendUpdateContactTool: ToolConfig<UpdateContactParams, UpdateContactResult> = {
id: 'resend_update_contact',
name: 'Update Contact',
@@ -42,7 +45,7 @@ export const resendUpdateContactTool: ToolConfig<UpdateContactParams, UpdateCont
request: {
url: (params: UpdateContactParams) =>
`https://api.resend.com/contacts/${encodeURIComponent(params.contactId)}`,
`https://api.resend.com/contacts/${encodeURIComponent(params.contactId.trim())}`,
method: 'PATCH',
headers: (params: UpdateContactParams) => ({
Authorization: `Bearer ${params.resendApiKey}`,
@@ -58,6 +61,17 @@ export const resendUpdateContactTool: ToolConfig<UpdateContactParams, UpdateCont
transformResponse: async (response: Response): Promise<UpdateContactResult> => {
const data = await response.json()
if (!data.id) {
logger.error('Resend Update Contact API error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.message || 'Failed to update contact',
output: {
id: '',
},
}
}
return {
success: true,
output: {