From bef36ec94554f7c81633bce006c6f8a7542242b6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 28 Feb 2026 11:09:56 -0800 Subject: [PATCH] improvement(resend): add error handling, authMode, and naming consistency --- apps/sim/blocks/blocks/resend.ts | 43 +++++++++++-------- apps/sim/tools/registry.ts | 4 +- apps/sim/tools/resend/create_contact.ts | 14 +++++++ apps/sim/tools/resend/delete_contact.ts | 19 ++++++++- apps/sim/tools/resend/get_contact.ts | 31 +++++++++++--- apps/sim/tools/resend/get_email.ts | 56 ++++++++++++++++++------- apps/sim/tools/resend/index.ts | 2 +- apps/sim/tools/resend/list_contacts.ts | 19 ++++++++- apps/sim/tools/resend/list_domains.ts | 19 ++++++++- apps/sim/tools/resend/send.ts | 2 +- apps/sim/tools/resend/update_contact.ts | 16 ++++++- 11 files changed, 176 insertions(+), 49 deletions(-) diff --git a/apps/sim/blocks/blocks/resend.ts b/apps/sim/blocks/blocks/resend.ts index eaf83943bd..75a06bcfaa 100644 --- a/apps/sim/blocks/blocks/resend.ts +++ b/apps/sim/blocks/blocks/resend.ts @@ -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' }, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index a0ac0eda4f..36c8e951a3 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -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 = { 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, diff --git a/apps/sim/tools/resend/create_contact.ts b/apps/sim/tools/resend/create_contact.ts index e350765753..de1154131b 100644 --- a/apps/sim/tools/resend/create_contact.ts +++ b/apps/sim/tools/resend/create_contact.ts @@ -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 = { id: 'resend_create_contact', name: 'Create Contact', @@ -58,6 +61,17 @@ export const resendCreateContactTool: ToolConfig => { 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: { diff --git a/apps/sim/tools/resend/delete_contact.ts b/apps/sim/tools/resend/delete_contact.ts index d9b1739a49..0035ab4e87 100644 --- a/apps/sim/tools/resend/delete_contact.ts +++ b/apps/sim/tools/resend/delete_contact.ts @@ -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 = { id: 'resend_delete_contact', name: 'Delete Contact', @@ -24,7 +27,7 @@ export const resendDeleteContactTool: ToolConfig - `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 => { 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, }, } diff --git a/apps/sim/tools/resend/get_contact.ts b/apps/sim/tools/resend/get_contact.ts index c521a5e725..75a1938a0f 100644 --- a/apps/sim/tools/resend/get_contact.ts +++ b/apps/sim/tools/resend/get_contact.ts @@ -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 = { id: 'resend_get_contact', name: 'Get Contact', @@ -24,7 +27,7 @@ export const resendGetContactTool: ToolConfig - `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 => { 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, }, } }, diff --git a/apps/sim/tools/resend/get_email.ts b/apps/sim/tools/resend/get_email.ts index 4803b46467..05b2cf8f78 100644 --- a/apps/sim/tools/resend/get_email.ts +++ b/apps/sim/tools/resend/get_email.ts @@ -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 = { id: 'resend_get_email', name: 'Get Email', @@ -23,7 +26,7 @@ export const resendGetEmailTool: ToolConfig = { }, 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 = { transformResponse: async (response: Response): Promise => { 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 = { 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' }, }, } diff --git a/apps/sim/tools/resend/index.ts b/apps/sim/tools/resend/index.ts index 846b46be5a..a8a7038154 100644 --- a/apps/sim/tools/resend/index.ts +++ b/apps/sim/tools/resend/index.ts @@ -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, diff --git a/apps/sim/tools/resend/list_contacts.ts b/apps/sim/tools/resend/list_contacts.ts index e3692beea8..808b473dd5 100644 --- a/apps/sim/tools/resend/list_contacts.ts +++ b/apps/sim/tools/resend/list_contacts.ts @@ -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 = { id: 'resend_list_contacts', name: 'List Contacts', @@ -28,11 +31,23 @@ export const resendListContactsTool: ToolConfig => { 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, }, } }, diff --git a/apps/sim/tools/resend/list_domains.ts b/apps/sim/tools/resend/list_domains.ts index f6fce0730a..bdc7fd4384 100644 --- a/apps/sim/tools/resend/list_domains.ts +++ b/apps/sim/tools/resend/list_domains.ts @@ -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 = { id: 'resend_list_domains', name: 'List Domains', @@ -28,10 +31,22 @@ export const resendListDomainsTool: ToolConfig => { 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 = { +export const resendSendTool: ToolConfig = { id: 'resend_send', name: 'Send Email', description: 'Send an email using your own Resend API key and from address', diff --git a/apps/sim/tools/resend/update_contact.ts b/apps/sim/tools/resend/update_contact.ts index 59d7bfb2cf..79df61129c 100644 --- a/apps/sim/tools/resend/update_contact.ts +++ b/apps/sim/tools/resend/update_contact.ts @@ -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 = { id: 'resend_update_contact', name: 'Update Contact', @@ -42,7 +45,7 @@ export const resendUpdateContactTool: ToolConfig - `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 => { 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: {