From abac7d1fb988dfa87292996a871ca9bc0b4c19a6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 29 Nov 2025 11:45:59 -0800 Subject: [PATCH 1/4] feat(tools): added twilio sendgrid integration --- apps/docs/components/icons.tsx | 42 ++ apps/docs/components/ui/icon-mapping.ts | 8 +- apps/docs/content/docs/en/tools/meta.json | 3 +- apps/docs/content/docs/en/tools/sendgrid.mdx | 372 ++++++++++++++ apps/sim/blocks/blocks/sendgrid.ts | 462 ++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 42 ++ apps/sim/tools/registry.ts | 34 ++ apps/sim/tools/sendgrid/add_contact.ts | 112 +++++ .../tools/sendgrid/add_contacts_to_list.ts | 74 +++ apps/sim/tools/sendgrid/create_list.ts | 64 +++ apps/sim/tools/sendgrid/create_template.ts | 75 +++ .../tools/sendgrid/create_template_version.ts | 117 +++++ apps/sim/tools/sendgrid/delete_contacts.ts | 58 +++ apps/sim/tools/sendgrid/delete_list.ts | 51 ++ apps/sim/tools/sendgrid/delete_template.ts | 46 ++ apps/sim/tools/sendgrid/get_contact.ts | 66 +++ apps/sim/tools/sendgrid/get_list.ts | 56 +++ apps/sim/tools/sendgrid/get_template.ts | 60 +++ apps/sim/tools/sendgrid/index.ts | 23 + apps/sim/tools/sendgrid/list_all_lists.ts | 58 +++ apps/sim/tools/sendgrid/list_templates.ts | 67 +++ .../sendgrid/remove_contacts_from_list.ts | 67 +++ apps/sim/tools/sendgrid/search_contacts.ts | 63 +++ apps/sim/tools/sendgrid/send_mail.ts | 208 ++++++++ apps/sim/tools/sendgrid/types.ts | 193 ++++++++ 26 files changed, 2419 insertions(+), 4 deletions(-) create mode 100644 apps/docs/content/docs/en/tools/sendgrid.mdx create mode 100644 apps/sim/blocks/blocks/sendgrid.ts create mode 100644 apps/sim/tools/sendgrid/add_contact.ts create mode 100644 apps/sim/tools/sendgrid/add_contacts_to_list.ts create mode 100644 apps/sim/tools/sendgrid/create_list.ts create mode 100644 apps/sim/tools/sendgrid/create_template.ts create mode 100644 apps/sim/tools/sendgrid/create_template_version.ts create mode 100644 apps/sim/tools/sendgrid/delete_contacts.ts create mode 100644 apps/sim/tools/sendgrid/delete_list.ts create mode 100644 apps/sim/tools/sendgrid/delete_template.ts create mode 100644 apps/sim/tools/sendgrid/get_contact.ts create mode 100644 apps/sim/tools/sendgrid/get_list.ts create mode 100644 apps/sim/tools/sendgrid/get_template.ts create mode 100644 apps/sim/tools/sendgrid/index.ts create mode 100644 apps/sim/tools/sendgrid/list_all_lists.ts create mode 100644 apps/sim/tools/sendgrid/list_templates.ts create mode 100644 apps/sim/tools/sendgrid/remove_contacts_from_list.ts create mode 100644 apps/sim/tools/sendgrid/search_contacts.ts create mode 100644 apps/sim/tools/sendgrid/send_mail.ts create mode 100644 apps/sim/tools/sendgrid/types.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 3c70935669a..38f990a2112 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4344,3 +4344,45 @@ export function PylonIcon(props: SVGProps) { ) } + +export function SendgridIcon(props: SVGProps) { + return ( + + + + + + + + + + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index ad748928c5b..3f52099d8c4 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -64,19 +64,20 @@ import { RedditIcon, ResendIcon, S3Icon, + STTIcon, SalesforceIcon, + SendgridIcon, SentryIcon, SerperIcon, SlackIcon, - STTIcon, StagehandIcon, StripeIcon, SupabaseIcon, + TTSIcon, TavilyIcon, TelegramIcon, TranslateIcon, TrelloIcon, - TTSIcon, TwilioIcon, TypeformIcon, VideoIcon, @@ -84,10 +85,10 @@ import { WebflowIcon, WhatsAppIcon, WikipediaIcon, - xIcon, YouTubeIcon, ZendeskIcon, ZepIcon, + xIcon, } from '@/components/icons' type IconComponent = ComponentType> @@ -121,6 +122,7 @@ export const blockTypeToIconMap: Record = { sharepoint: MicrosoftSharepointIcon, serper: SerperIcon, sentry: SentryIcon, + sendgrid: SendgridIcon, salesforce: SalesforceIcon, s3: S3Icon, resend: ResendIcon, diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 6f83cb4500f..8cd77e75698 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -60,6 +60,7 @@ "resend", "s3", "salesforce", + "sendgrid", "sentry", "serper", "sharepoint", @@ -89,4 +90,4 @@ "zendesk", "zep" ] -} +} \ No newline at end of file diff --git a/apps/docs/content/docs/en/tools/sendgrid.mdx b/apps/docs/content/docs/en/tools/sendgrid.mdx new file mode 100644 index 00000000000..0b9c8ae65e0 --- /dev/null +++ b/apps/docs/content/docs/en/tools/sendgrid.mdx @@ -0,0 +1,372 @@ +--- +title: SendGrid +description: Send emails and manage contacts, lists, and templates with SendGrid +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +## Usage Instructions + +Integrate SendGrid into your workflow. Send transactional emails, manage marketing contacts and lists, and work with email templates. Supports dynamic templates, attachments, and comprehensive contact management. + + + +## Tools + +### `sendgrid_send_mail` + +Send an email using SendGrid API + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `from` | string | Yes | Sender email address \(must be verified in SendGrid\) | +| `fromName` | string | No | Sender name | +| `to` | string | Yes | Recipient email address | +| `toName` | string | No | Recipient name | +| `subject` | string | No | Email subject \(required unless using a template with pre-defined subject\) | +| `content` | string | No | Email body content \(required unless using a template with pre-defined content\) | +| `contentType` | string | No | Content type \(text/plain or text/html\) | +| `cc` | string | No | CC email address | +| `bcc` | string | No | BCC email address | +| `replyTo` | string | No | Reply-to email address | +| `replyToName` | string | No | Reply-to name | +| `attachments` | json | No | JSON array of attachment objects with content \(base64\), filename, type, and disposition | +| `templateId` | string | No | SendGrid template ID to use | +| `dynamicTemplateData` | json | No | JSON object of dynamic template data | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the email was sent successfully | +| `messageId` | string | SendGrid message ID | +| `to` | string | Recipient email address | +| `subject` | string | Email subject | + +### `sendgrid_add_contact` + +Add a new contact to SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `email` | string | Yes | Contact email address | +| `firstName` | string | No | Contact first name | +| `lastName` | string | No | Contact last name | +| `customFields` | json | No | JSON object of custom field key-value pairs | +| `listIds` | string | No | Comma-separated list IDs to add the contact to | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `jobId` | string | Job ID for tracking the async contact creation | +| `email` | string | Contact email address | +| `firstName` | string | Contact first name | +| `lastName` | string | Contact last name | +| `message` | string | Status message | + +### `sendgrid_get_contact` + +Get a specific contact by ID from SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `contactId` | string | Yes | Contact ID | + +#### 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 | Creation timestamp | +| `updatedAt` | string | Last update timestamp | +| `listIds` | json | Array of list IDs the contact belongs to | +| `customFields` | json | Custom field values | + +### `sendgrid_search_contacts` + +Search for contacts in SendGrid using a query + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `query` | string | Yes | Search query \(e.g., \"email LIKE '%example.com%' AND CONTAINS\(list_ids, 'list-id'\)\"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `contacts` | json | Array of matching contacts | +| `contactCount` | number | Total number of contacts found | + +### `sendgrid_delete_contacts` + +Delete one or more contacts from SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `contactIds` | string | Yes | Comma-separated contact IDs to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `jobId` | string | Job ID for the deletion request | + +### `sendgrid_create_list` + +Create a new contact list in SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `name` | string | Yes | List name | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | List ID | +| `name` | string | List name | +| `contactCount` | number | Number of contacts in the list | + +### `sendgrid_get_list` + +Get a specific list by ID from SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `listId` | string | Yes | List ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | List ID | +| `name` | string | List name | +| `contactCount` | number | Number of contacts in the list | + +### `sendgrid_list_all_lists` + +Get all contact lists from SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `pageSize` | number | No | Number of lists to return per page \(default: 100\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `lists` | json | Array of lists | + +### `sendgrid_delete_list` + +Delete a contact list from SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `listId` | string | Yes | List ID to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Success message | + +### `sendgrid_add_contacts_to_list` + +Add or update contacts and assign them to a list in SendGrid (uses PUT /v3/marketing/contacts) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `listId` | string | Yes | List ID to add contacts to | +| `contacts` | json | Yes | JSON array of contact objects. Each contact must have at least: email \(or phone_number_id/external_id/anonymous_id\). Example: \[\{"email": "user@example.com", "first_name": "John"\}\] | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `jobId` | string | Job ID for tracking the async operation | +| `message` | string | Status message | + +### `sendgrid_remove_contacts_from_list` + +Remove contacts from a specific list in SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `listId` | string | Yes | List ID | +| `contactIds` | string | Yes | Comma-separated contact IDs to remove from the list | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `jobId` | string | Job ID for the request | + +### `sendgrid_create_template` + +Create a new email template in SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `name` | string | Yes | Template name | +| `generation` | string | No | Template generation type \(legacy or dynamic, default: dynamic\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Template ID | +| `name` | string | Template name | +| `generation` | string | Template generation | +| `updatedAt` | string | Last update timestamp | +| `versions` | json | Array of template versions | + +### `sendgrid_get_template` + +Get a specific template by ID from SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `templateId` | string | Yes | Template ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Template ID | +| `name` | string | Template name | +| `generation` | string | Template generation | +| `updatedAt` | string | Last update timestamp | +| `versions` | json | Array of template versions | + +### `sendgrid_list_templates` + +Get all email templates from SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `generations` | string | No | Filter by generation \(legacy, dynamic, or both\) | +| `pageSize` | number | No | Number of templates to return per page \(default: 20\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `templates` | json | Array of templates | + +### `sendgrid_delete_template` + +Delete an email template from SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `templateId` | string | Yes | Template ID to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Operation success status | +| `messageId` | string | Email message ID \(send_mail\) | +| `id` | string | Resource ID | +| `jobId` | string | Job ID for async operations | +| `email` | string | Email address | +| `firstName` | string | First name | +| `lastName` | string | Last name | +| `contacts` | json | Array of contacts | +| `contactCount` | number | Number of contacts | +| `lists` | json | Array of lists | +| `templates` | json | Array of templates | +| `message` | string | Status or success message | +| `name` | string | Resource name | +| `generation` | string | Template generation | + +### `sendgrid_create_template_version` + +Create a new version of an email template in SendGrid + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | SendGrid API key | +| `templateId` | string | Yes | Template ID | +| `name` | string | Yes | Version name | +| `subject` | string | Yes | Email subject line | +| `htmlContent` | string | No | HTML content of the template | +| `plainContent` | string | No | Plain text content of the template | +| `active` | boolean | No | Whether this version is active \(default: true\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Version ID | +| `templateId` | string | Template ID | +| `name` | string | Version name | +| `subject` | string | Email subject | +| `active` | boolean | Whether this version is active | +| `htmlContent` | string | HTML content | +| `plainContent` | string | Plain text content | +| `updatedAt` | string | Last update timestamp | + + + +## Notes + +- Category: `tools` +- Type: `sendgrid` diff --git a/apps/sim/blocks/blocks/sendgrid.ts b/apps/sim/blocks/blocks/sendgrid.ts new file mode 100644 index 00000000000..4f083a7abc0 --- /dev/null +++ b/apps/sim/blocks/blocks/sendgrid.ts @@ -0,0 +1,462 @@ +import { SendgridIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import type { SendMailResult } from '@/tools/sendgrid/types' + +export const SendGridBlock: BlockConfig = { + type: 'sendgrid', + name: 'SendGrid', + description: 'Send emails and manage contacts, lists, and templates with SendGrid', + longDescription: + 'Integrate SendGrid into your workflow. Send transactional emails, manage marketing contacts and lists, and work with email templates. Supports dynamic templates, attachments, and comprehensive contact management.', + docsLink: 'https://docs.sim.ai/tools/sendgrid', + category: 'tools', + bgColor: '#1A82E2', + icon: SendgridIcon, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + // Mail Operations + { label: 'Send Mail', id: 'send_mail' }, + // Contact Operations + { label: 'Add Contact', id: 'add_contact' }, + { label: 'Get Contact', id: 'get_contact' }, + { label: 'Search Contacts', id: 'search_contacts' }, + { label: 'Delete Contacts', id: 'delete_contacts' }, + // List Operations + { label: 'Create List', id: 'create_list' }, + { label: 'Get List', id: 'get_list' }, + { label: 'List All Lists', id: 'list_all_lists' }, + { label: 'Delete List', id: 'delete_list' }, + { label: 'Add Contacts to List', id: 'add_contacts_to_list' }, + { label: 'Remove Contacts from List', id: 'remove_contacts_from_list' }, + // Template Operations + { label: 'Create Template', id: 'create_template' }, + { label: 'Get Template', id: 'get_template' }, + { label: 'List Templates', id: 'list_templates' }, + { label: 'Delete Template', id: 'delete_template' }, + { label: 'Create Template Version', id: 'create_template_version' }, + ], + value: () => 'send_mail', + }, + { + id: 'apiKey', + title: 'SendGrid API Key', + type: 'short-input', + password: true, + placeholder: 'Enter your SendGrid API key', + required: true, + }, + // Send Mail fields + { + id: 'from', + title: 'From Email', + type: 'short-input', + placeholder: 'sender@yourdomain.com', + condition: { field: 'operation', value: 'send_mail' }, + required: true, + }, + { + id: 'fromName', + title: 'From Name', + type: 'short-input', + placeholder: 'Sender Name', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'to', + title: 'To Email', + type: 'short-input', + placeholder: 'recipient@example.com', + condition: { field: 'operation', value: 'send_mail' }, + required: true, + }, + { + id: 'toName', + title: 'To Name', + type: 'short-input', + placeholder: 'Recipient Name', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'mailSubject', + title: 'Subject', + type: 'short-input', + placeholder: 'Email subject (required unless using template)', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'content', + title: 'Content', + type: 'long-input', + placeholder: 'Email body content (required unless using template)', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'Plain Text', id: 'text/plain' }, + { label: 'HTML', id: 'text/html' }, + ], + value: () => 'text/plain', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'cc', + title: 'CC', + type: 'short-input', + placeholder: 'cc@example.com', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'bcc', + title: 'BCC', + type: 'short-input', + placeholder: 'bcc@example.com', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'replyTo', + title: 'Reply To', + type: 'short-input', + placeholder: 'replyto@example.com', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'replyToName', + title: 'Reply To Name', + type: 'short-input', + placeholder: 'Reply To Name', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'mailTemplateId', + title: 'Template ID', + type: 'short-input', + placeholder: 'SendGrid template ID', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'dynamicTemplateData', + title: 'Dynamic Template Data', + type: 'code', + placeholder: '{"name": "John", "order_id": "12345"}', + condition: { field: 'operation', value: 'send_mail' }, + }, + { + id: 'attachments', + title: 'Attachments', + type: 'code', + placeholder: + '[{"content": "base64string", "filename": "file.pdf", "type": "application/pdf"}]', + condition: { field: 'operation', value: 'send_mail' }, + }, + // Contact fields + { + id: 'email', + title: 'Email', + type: 'short-input', + placeholder: 'contact@example.com', + condition: { field: 'operation', value: ['add_contact'] }, + required: true, + }, + { + id: 'firstName', + title: 'First Name', + type: 'short-input', + placeholder: 'John', + condition: { field: 'operation', value: ['add_contact'] }, + }, + { + id: 'lastName', + title: 'Last Name', + type: 'short-input', + placeholder: 'Doe', + condition: { field: 'operation', value: ['add_contact'] }, + }, + { + id: 'customFields', + title: 'Custom Fields', + type: 'code', + placeholder: '{"custom_field_1": "value1"}', + condition: { field: 'operation', value: ['add_contact'] }, + }, + { + id: 'contactListIds', + title: 'List IDs', + type: 'short-input', + placeholder: 'Comma-separated list IDs', + condition: { field: 'operation', value: ['add_contact'] }, + }, + { + id: 'contactId', + title: 'Contact ID', + type: 'short-input', + placeholder: 'Contact ID', + condition: { field: 'operation', value: ['get_contact'] }, + required: true, + }, + { + id: 'query', + title: 'Search Query', + type: 'long-input', + placeholder: "email LIKE '%example.com%'", + condition: { field: 'operation', value: ['search_contacts'] }, + required: true, + }, + { + id: 'contactIds', + title: 'Contact IDs', + type: 'short-input', + placeholder: 'Comma-separated contact IDs', + condition: { + field: 'operation', + value: ['delete_contacts', 'remove_contacts_from_list'], + }, + required: true, + }, + { + id: 'contacts', + title: 'Contacts (JSON Array)', + type: 'code', + placeholder: '[{"email": "user@example.com", "first_name": "John"}]', + condition: { field: 'operation', value: 'add_contacts_to_list' }, + required: true, + }, + // List fields + { + id: 'listName', + title: 'List Name', + type: 'short-input', + placeholder: 'List name', + condition: { field: 'operation', value: ['create_list'] }, + required: true, + }, + { + id: 'listId', + title: 'List ID', + type: 'short-input', + placeholder: 'List ID', + condition: { + field: 'operation', + value: ['get_list', 'delete_list', 'add_contacts_to_list', 'remove_contacts_from_list'], + }, + required: true, + }, + { + id: 'listPageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '100', + condition: { field: 'operation', value: 'list_all_lists' }, + }, + // Template fields + { + id: 'templateName', + title: 'Template Name', + type: 'short-input', + placeholder: 'Template name', + condition: { field: 'operation', value: ['create_template'] }, + required: true, + }, + { + id: 'templateId', + title: 'Template ID', + type: 'short-input', + placeholder: 'Template ID', + condition: { + field: 'operation', + value: ['get_template', 'delete_template', 'create_template_version'], + }, + required: true, + }, + { + id: 'generation', + title: 'Template Generation', + type: 'dropdown', + options: [ + { label: 'Dynamic', id: 'dynamic' }, + { label: 'Legacy', id: 'legacy' }, + ], + value: () => 'dynamic', + condition: { field: 'operation', value: 'create_template' }, + }, + { + id: 'templateGenerations', + title: 'Filter by Generation', + type: 'short-input', + placeholder: 'legacy, dynamic, or both', + condition: { field: 'operation', value: 'list_templates' }, + }, + { + id: 'templatePageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '20', + condition: { field: 'operation', value: 'list_templates' }, + }, + { + id: 'versionName', + title: 'Version Name', + type: 'short-input', + placeholder: 'Version name', + condition: { field: 'operation', value: 'create_template_version' }, + required: true, + }, + { + id: 'templateSubject', + title: 'Template Subject', + type: 'short-input', + placeholder: 'Email subject', + condition: { field: 'operation', value: 'create_template_version' }, + required: true, + }, + { + id: 'htmlContent', + title: 'HTML Content', + type: 'code', + placeholder: '{{name}}', + condition: { field: 'operation', value: 'create_template_version' }, + }, + { + id: 'plainContent', + title: 'Plain Text Content', + type: 'long-input', + placeholder: 'Plain text content', + condition: { field: 'operation', value: 'create_template_version' }, + }, + { + id: 'active', + title: 'Active', + type: 'dropdown', + options: [ + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'true', + condition: { field: 'operation', value: 'create_template_version' }, + }, + ], + + tools: { + access: [ + 'sendgrid_send_mail', + 'sendgrid_add_contact', + 'sendgrid_get_contact', + 'sendgrid_search_contacts', + 'sendgrid_delete_contacts', + 'sendgrid_create_list', + 'sendgrid_get_list', + 'sendgrid_list_all_lists', + 'sendgrid_delete_list', + 'sendgrid_add_contacts_to_list', + 'sendgrid_remove_contacts_from_list', + 'sendgrid_create_template', + 'sendgrid_get_template', + 'sendgrid_list_templates', + 'sendgrid_delete_template', + 'sendgrid_create_template_version', + ], + config: { + tool: (params) => `sendgrid_${params.operation}`, + params: (params) => { + const { + operation, + mailSubject, + mailTemplateId, + listName, + templateName, + versionName, + templateSubject, + contactListIds, + templateGenerations, + listPageSize, + templatePageSize, + ...rest + } = params + + // Map renamed fields back to tool parameter names + return { + ...rest, + ...(mailSubject && { subject: mailSubject }), + ...(mailTemplateId && { templateId: mailTemplateId }), + ...(listName && { name: listName }), + ...(templateName && { name: templateName }), + ...(versionName && { name: versionName }), + ...(templateSubject && { subject: templateSubject }), + ...(contactListIds && { listIds: contactListIds }), + ...(templateGenerations && { generations: templateGenerations }), + ...(listPageSize && { pageSize: listPageSize }), + ...(templatePageSize && { pageSize: templatePageSize }), + } + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'SendGrid API key' }, + // Mail inputs + from: { type: 'string', description: 'Sender email address' }, + fromName: { type: 'string', description: 'Sender name' }, + to: { type: 'string', description: 'Recipient email address' }, + toName: { type: 'string', description: 'Recipient name' }, + mailSubject: { type: 'string', description: 'Email subject' }, + content: { type: 'string', description: 'Email content' }, + contentType: { type: 'string', description: 'Content type' }, + cc: { type: 'string', description: 'CC email address' }, + bcc: { type: 'string', description: 'BCC email address' }, + replyTo: { type: 'string', description: 'Reply-to email address' }, + replyToName: { type: 'string', description: 'Reply-to name' }, + mailTemplateId: { type: 'string', description: 'Template ID for sending mail' }, + dynamicTemplateData: { type: 'json', description: 'Dynamic template data' }, + attachments: { type: 'json', description: 'Email attachments' }, + // Contact inputs + email: { type: 'string', description: 'Contact email' }, + firstName: { type: 'string', description: 'Contact first name' }, + lastName: { type: 'string', description: 'Contact last name' }, + customFields: { type: 'json', description: 'Custom fields' }, + contactId: { type: 'string', description: 'Contact ID' }, + contactIds: { type: 'string', description: 'Comma-separated contact IDs' }, + contacts: { type: 'json', description: 'Array of contact objects' }, + query: { type: 'string', description: 'Search query' }, + contactListIds: { type: 'string', description: 'Comma-separated list IDs for contact' }, + // List inputs + listName: { type: 'string', description: 'List name' }, + listId: { type: 'string', description: 'List ID' }, + listPageSize: { type: 'number', description: 'Page size for listing lists' }, + // Template inputs + templateName: { type: 'string', description: 'Template name' }, + templateId: { type: 'string', description: 'Template ID' }, + generation: { type: 'string', description: 'Template generation' }, + templateGenerations: { type: 'string', description: 'Filter templates by generation' }, + templatePageSize: { type: 'number', description: 'Page size for listing templates' }, + versionName: { type: 'string', description: 'Template version name' }, + templateSubject: { type: 'string', description: 'Template subject' }, + htmlContent: { type: 'string', description: 'HTML content' }, + plainContent: { type: 'string', description: 'Plain text content' }, + active: { type: 'boolean', description: 'Whether template version is active' }, + }, + + outputs: { + success: { type: 'boolean', description: 'Operation success status' }, + messageId: { type: 'string', description: 'Email message ID (send_mail)' }, + id: { type: 'string', description: 'Resource ID' }, + jobId: { type: 'string', description: 'Job ID for async operations' }, + email: { type: 'string', description: 'Email address' }, + firstName: { type: 'string', description: 'First name' }, + lastName: { type: 'string', description: 'Last name' }, + contacts: { type: 'json', description: 'Array of contacts' }, + contactCount: { type: 'number', description: 'Number of contacts' }, + lists: { type: 'json', description: 'Array of lists' }, + templates: { type: 'json', description: 'Array of templates' }, + message: { type: 'string', description: 'Status or success message' }, + name: { type: 'string', description: 'Resource name' }, + generation: { type: 'string', description: 'Template generation' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index db663ba3ec6..2a97975ae5f 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -74,6 +74,7 @@ import { RouterBlock } from '@/blocks/blocks/router' import { S3Block } from '@/blocks/blocks/s3' import { SalesforceBlock } from '@/blocks/blocks/salesforce' import { ScheduleBlock } from '@/blocks/blocks/schedule' +import { SendGridBlock } from '@/blocks/blocks/sendgrid' import { SentryBlock } from '@/blocks/blocks/sentry' import { SerperBlock } from '@/blocks/blocks/serper' import { SharepointBlock } from '@/blocks/blocks/sharepoint' @@ -178,6 +179,7 @@ export const registry: Record = { pylon: PylonBlock, qdrant: QdrantBlock, resend: ResendBlock, + sendgrid: SendGridBlock, memory: MemoryBlock, reddit: RedditBlock, response: ResponseBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 3c70935669a..38f990a2112 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4344,3 +4344,45 @@ export function PylonIcon(props: SVGProps) { ) } + +export function SendgridIcon(props: SVGProps) { + return ( + + + + + + + + + + + ) +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index f22d04cd20a..be2ae914404 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -756,6 +756,24 @@ import { salesforceUpdateOpportunityTool, salesforceUpdateTaskTool, } from '@/tools/salesforce' +import { + sendGridAddContactsToListTool, + sendGridAddContactTool, + sendGridCreateListTool, + sendGridCreateTemplateTool, + sendGridCreateTemplateVersionTool, + sendGridDeleteContactsTool, + sendGridDeleteListTool, + sendGridDeleteTemplateTool, + sendGridGetContactTool, + sendGridGetListTool, + sendGridGetTemplateTool, + sendGridListAllListsTool, + sendGridListTemplatesTool, + sendGridRemoveContactsFromListTool, + sendGridSearchContactsTool, + sendGridSendMailTool, +} from '@/tools/sendgrid' import { createDeployTool, createProjectTool, @@ -1026,6 +1044,22 @@ export const tools: Record = { jina_search: jinaSearchTool, linkup_search: linkupSearchTool, resend_send: mailSendTool, + sendgrid_send_mail: sendGridSendMailTool, + sendgrid_add_contact: sendGridAddContactTool, + sendgrid_get_contact: sendGridGetContactTool, + sendgrid_search_contacts: sendGridSearchContactsTool, + sendgrid_delete_contacts: sendGridDeleteContactsTool, + sendgrid_create_list: sendGridCreateListTool, + sendgrid_get_list: sendGridGetListTool, + sendgrid_list_all_lists: sendGridListAllListsTool, + sendgrid_delete_list: sendGridDeleteListTool, + sendgrid_add_contacts_to_list: sendGridAddContactsToListTool, + sendgrid_remove_contacts_from_list: sendGridRemoveContactsFromListTool, + sendgrid_create_template: sendGridCreateTemplateTool, + sendgrid_get_template: sendGridGetTemplateTool, + sendgrid_list_templates: sendGridListTemplatesTool, + sendgrid_delete_template: sendGridDeleteTemplateTool, + sendgrid_create_template_version: sendGridCreateTemplateVersionTool, sms_send: smsSendTool, jira_retrieve: jiraRetrieveTool, jira_update: jiraUpdateTool, diff --git a/apps/sim/tools/sendgrid/add_contact.ts b/apps/sim/tools/sendgrid/add_contact.ts new file mode 100644 index 00000000000..c89d2a7814e --- /dev/null +++ b/apps/sim/tools/sendgrid/add_contact.ts @@ -0,0 +1,112 @@ +import type { AddContactParams, ContactResult } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridAddContactTool: ToolConfig = { + id: 'sendgrid_add_contact', + name: 'SendGrid Add Contact', + description: 'Add a new contact to SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + email: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Contact email address', + }, + firstName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Contact first name', + }, + lastName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Contact last name', + }, + customFields: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON object of custom field key-value pairs', + }, + listIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list IDs to add the contact to', + }, + }, + + request: { + url: () => 'https://api.sendgrid.com/v3/marketing/contacts', + method: 'PUT', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const contact: any = { + email: params.email, + } + + if (params.firstName) contact.first_name = params.firstName + if (params.lastName) contact.last_name = params.lastName + + if (params.customFields) { + const customFields = + typeof params.customFields === 'string' + ? JSON.parse(params.customFields) + : params.customFields + Object.assign(contact, customFields) + } + + const body: any = { + contacts: [contact], + } + + if (params.listIds) { + body.list_ids = params.listIds.split(',').map((id) => id.trim()) + } + + return { body: JSON.stringify(body) } + }, + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to add contact') + } + + const data = await response.json() + + return { + success: true, + output: { + jobId: data.job_id, + email: params?.email || '', + firstName: params?.firstName, + lastName: params?.lastName, + message: + 'Contact is being added. This is an asynchronous operation. Use the job ID to track status.', + }, + } + }, + + outputs: { + jobId: { type: 'string', description: 'Job ID for tracking the async contact creation' }, + email: { type: 'string', description: 'Contact email address' }, + firstName: { type: 'string', description: 'Contact first name' }, + lastName: { type: 'string', description: 'Contact last name' }, + message: { type: 'string', description: 'Status message' }, + }, +} diff --git a/apps/sim/tools/sendgrid/add_contacts_to_list.ts b/apps/sim/tools/sendgrid/add_contacts_to_list.ts new file mode 100644 index 00000000000..cdd943d361b --- /dev/null +++ b/apps/sim/tools/sendgrid/add_contacts_to_list.ts @@ -0,0 +1,74 @@ +import type { AddContactsToListParams } from '@/tools/sendgrid/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const sendGridAddContactsToListTool: ToolConfig = { + id: 'sendgrid_add_contacts_to_list', + name: 'SendGrid Add Contacts to List', + description: + 'Add or update contacts and assign them to a list in SendGrid (uses PUT /v3/marketing/contacts)', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + listId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'List ID to add contacts to', + }, + contacts: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'JSON array of contact objects. Each contact must have at least: email (or phone_number_id/external_id/anonymous_id). Example: [{"email": "user@example.com", "first_name": "John"}]', + }, + }, + + request: { + url: () => 'https://api.sendgrid.com/v3/marketing/contacts', + method: 'PUT', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const contactsArray = + typeof params.contacts === 'string' ? JSON.parse(params.contacts) : params.contacts + + return { + body: JSON.stringify({ + list_ids: [params.listId], + contacts: contactsArray, + }), + } + }, + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to add contacts to list') + } + + const data = await response.json() + + return { + success: true, + output: { + jobId: data.job_id, + message: 'Contacts are being added to the list. This is an asynchronous operation.', + }, + } + }, + + outputs: { + jobId: { type: 'string', description: 'Job ID for tracking the async operation' }, + message: { type: 'string', description: 'Status message' }, + }, +} diff --git a/apps/sim/tools/sendgrid/create_list.ts b/apps/sim/tools/sendgrid/create_list.ts new file mode 100644 index 00000000000..e3e1b3d0f52 --- /dev/null +++ b/apps/sim/tools/sendgrid/create_list.ts @@ -0,0 +1,64 @@ +import type { CreateListParams, ListResult } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridCreateListTool: ToolConfig = { + id: 'sendgrid_create_list', + name: 'SendGrid Create List', + description: 'Create a new contact list in SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'List name', + }, + }, + + request: { + url: () => 'https://api.sendgrid.com/v3/marketing/lists', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + return { + body: JSON.stringify({ + name: params.name, + }), + } + }, + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to create list') + } + + const data = await response.json() + + return { + success: true, + output: { + id: data.id, + name: data.name, + contactCount: data.contact_count, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'List ID' }, + name: { type: 'string', description: 'List name' }, + contactCount: { type: 'number', description: 'Number of contacts in the list' }, + }, +} diff --git a/apps/sim/tools/sendgrid/create_template.ts b/apps/sim/tools/sendgrid/create_template.ts new file mode 100644 index 00000000000..73380e122c3 --- /dev/null +++ b/apps/sim/tools/sendgrid/create_template.ts @@ -0,0 +1,75 @@ +import type { CreateTemplateParams, TemplateResult } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridCreateTemplateTool: ToolConfig = { + id: 'sendgrid_create_template', + name: 'SendGrid Create Template', + description: 'Create a new email template in SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Template name', + }, + generation: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Template generation type (legacy or dynamic, default: dynamic)', + }, + }, + + request: { + url: () => 'https://api.sendgrid.com/v3/templates', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + return { + body: JSON.stringify({ + name: params.name, + generation: params.generation || 'dynamic', + }), + } + }, + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to create template') + } + + const data = await response.json() + + return { + success: true, + output: { + id: data.id, + name: data.name, + generation: data.generation, + updatedAt: data.updated_at, + versions: data.versions || [], + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Template ID' }, + name: { type: 'string', description: 'Template name' }, + generation: { type: 'string', description: 'Template generation' }, + updatedAt: { type: 'string', description: 'Last update timestamp' }, + versions: { type: 'json', description: 'Array of template versions' }, + }, +} diff --git a/apps/sim/tools/sendgrid/create_template_version.ts b/apps/sim/tools/sendgrid/create_template_version.ts new file mode 100644 index 00000000000..643b486a36f --- /dev/null +++ b/apps/sim/tools/sendgrid/create_template_version.ts @@ -0,0 +1,117 @@ +import type { CreateTemplateVersionParams, TemplateVersionResult } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridCreateTemplateVersionTool: ToolConfig< + CreateTemplateVersionParams, + TemplateVersionResult +> = { + id: 'sendgrid_create_template_version', + name: 'SendGrid Create Template Version', + description: 'Create a new version of an email template in SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + templateId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Template ID', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Version name', + }, + subject: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email subject line', + }, + htmlContent: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'HTML content of the template', + }, + plainContent: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Plain text content of the template', + }, + active: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether this version is active (default: true)', + }, + }, + + request: { + url: (params) => `https://api.sendgrid.com/v3/templates/${params.templateId}/versions`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: any = { + name: params.name, + subject: params.subject, + active: params.active !== undefined ? params.active : 1, + } + + if (params.htmlContent) { + body.html_content = params.htmlContent + } + + if (params.plainContent) { + body.plain_content = params.plainContent + } + + return { body: JSON.stringify(body) } + }, + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to create template version') + } + + const data = await response.json() + + return { + success: true, + output: { + id: data.id, + templateId: data.template_id, + name: data.name, + subject: data.subject, + active: data.active === 1, + htmlContent: data.html_content, + plainContent: data.plain_content, + updatedAt: data.updated_at, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Version ID' }, + templateId: { type: 'string', description: 'Template ID' }, + name: { type: 'string', description: 'Version name' }, + subject: { type: 'string', description: 'Email subject' }, + active: { type: 'boolean', description: 'Whether this version is active' }, + htmlContent: { type: 'string', description: 'HTML content' }, + plainContent: { type: 'string', description: 'Plain text content' }, + updatedAt: { type: 'string', description: 'Last update timestamp' }, + }, +} diff --git a/apps/sim/tools/sendgrid/delete_contacts.ts b/apps/sim/tools/sendgrid/delete_contacts.ts new file mode 100644 index 00000000000..cb0d21ccbb1 --- /dev/null +++ b/apps/sim/tools/sendgrid/delete_contacts.ts @@ -0,0 +1,58 @@ +import type { DeleteContactParams } from '@/tools/sendgrid/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const sendGridDeleteContactsTool: ToolConfig = { + id: 'sendgrid_delete_contacts', + name: 'SendGrid Delete Contacts', + description: 'Delete one or more contacts from SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + contactIds: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated contact IDs to delete', + }, + }, + + request: { + url: (params) => { + const ids = params.contactIds + .split(',') + .map((id) => id.trim()) + .join(',') + return `https://api.sendgrid.com/v3/marketing/contacts?ids=${encodeURIComponent(ids)}` + }, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to delete contacts') + } + + const data = await response.json() + + return { + success: true, + output: { + jobId: data.job_id, + }, + } + }, + + outputs: { + jobId: { type: 'string', description: 'Job ID for the deletion request' }, + }, +} diff --git a/apps/sim/tools/sendgrid/delete_list.ts b/apps/sim/tools/sendgrid/delete_list.ts new file mode 100644 index 00000000000..8f029cb8ebe --- /dev/null +++ b/apps/sim/tools/sendgrid/delete_list.ts @@ -0,0 +1,51 @@ +import type { DeleteListParams } from '@/tools/sendgrid/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const sendGridDeleteListTool: ToolConfig = { + id: 'sendgrid_delete_list', + name: 'SendGrid Delete List', + description: 'Delete a contact list from SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + listId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'List ID to delete', + }, + }, + + request: { + url: (params) => `https://api.sendgrid.com/v3/marketing/lists/${params.listId}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to delete list') + } + + // API returns 204 No Content on success + return { + success: true, + output: { + message: 'List deleted successfully', + }, + } + }, + + outputs: { + message: { type: 'string', description: 'Success message' }, + }, +} diff --git a/apps/sim/tools/sendgrid/delete_template.ts b/apps/sim/tools/sendgrid/delete_template.ts new file mode 100644 index 00000000000..c585450f130 --- /dev/null +++ b/apps/sim/tools/sendgrid/delete_template.ts @@ -0,0 +1,46 @@ +import type { DeleteTemplateParams } from '@/tools/sendgrid/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const sendGridDeleteTemplateTool: ToolConfig = { + id: 'sendgrid_delete_template', + name: 'SendGrid Delete Template', + description: 'Delete an email template from SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + templateId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Template ID to delete', + }, + }, + + request: { + url: (params) => `https://api.sendgrid.com/v3/templates/${params.templateId}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to delete template') + } + + return { + success: true, + output: {}, + } + }, + + outputs: {}, +} diff --git a/apps/sim/tools/sendgrid/get_contact.ts b/apps/sim/tools/sendgrid/get_contact.ts new file mode 100644 index 00000000000..3485df6eb57 --- /dev/null +++ b/apps/sim/tools/sendgrid/get_contact.ts @@ -0,0 +1,66 @@ +import type { ContactResult, GetContactParams } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridGetContactTool: ToolConfig = { + id: 'sendgrid_get_contact', + name: 'SendGrid Get Contact', + description: 'Get a specific contact by ID from SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + contactId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Contact ID', + }, + }, + + request: { + url: (params) => `https://api.sendgrid.com/v3/marketing/contacts/${params.contactId}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to get contact') + } + + 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, + updatedAt: data.updated_at, + listIds: data.list_ids, + customFields: data.custom_fields, + }, + } + }, + + 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: 'Creation timestamp' }, + updatedAt: { type: 'string', description: 'Last update timestamp' }, + listIds: { type: 'json', description: 'Array of list IDs the contact belongs to' }, + customFields: { type: 'json', description: 'Custom field values' }, + }, +} diff --git a/apps/sim/tools/sendgrid/get_list.ts b/apps/sim/tools/sendgrid/get_list.ts new file mode 100644 index 00000000000..4cda9d65ce4 --- /dev/null +++ b/apps/sim/tools/sendgrid/get_list.ts @@ -0,0 +1,56 @@ +import type { GetListParams, ListResult } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridGetListTool: ToolConfig = { + id: 'sendgrid_get_list', + name: 'SendGrid Get List', + description: 'Get a specific list by ID from SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + listId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'List ID', + }, + }, + + request: { + url: (params) => `https://api.sendgrid.com/v3/marketing/lists/${params.listId}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to get list') + } + + const data = await response.json() + + return { + success: true, + output: { + id: data.id, + name: data.name, + contactCount: data.contact_count, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'List ID' }, + name: { type: 'string', description: 'List name' }, + contactCount: { type: 'number', description: 'Number of contacts in the list' }, + }, +} diff --git a/apps/sim/tools/sendgrid/get_template.ts b/apps/sim/tools/sendgrid/get_template.ts new file mode 100644 index 00000000000..73bf6d65669 --- /dev/null +++ b/apps/sim/tools/sendgrid/get_template.ts @@ -0,0 +1,60 @@ +import type { GetTemplateParams, TemplateResult } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridGetTemplateTool: ToolConfig = { + id: 'sendgrid_get_template', + name: 'SendGrid Get Template', + description: 'Get a specific template by ID from SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + templateId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Template ID', + }, + }, + + request: { + url: (params) => `https://api.sendgrid.com/v3/templates/${params.templateId}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to get template') + } + + const data = await response.json() + + return { + success: true, + output: { + id: data.id, + name: data.name, + generation: data.generation, + updatedAt: data.updated_at, + versions: data.versions || [], + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Template ID' }, + name: { type: 'string', description: 'Template name' }, + generation: { type: 'string', description: 'Template generation' }, + updatedAt: { type: 'string', description: 'Last update timestamp' }, + versions: { type: 'json', description: 'Array of template versions' }, + }, +} diff --git a/apps/sim/tools/sendgrid/index.ts b/apps/sim/tools/sendgrid/index.ts new file mode 100644 index 00000000000..0a716cc07b4 --- /dev/null +++ b/apps/sim/tools/sendgrid/index.ts @@ -0,0 +1,23 @@ +// Mail Send + +// Contact Management +export { sendGridAddContactTool } from './add_contact' +export { sendGridAddContactsToListTool } from './add_contacts_to_list' +// List Management +export { sendGridCreateListTool } from './create_list' +// Template Management +export { sendGridCreateTemplateTool } from './create_template' +export { sendGridCreateTemplateVersionTool } from './create_template_version' +export { sendGridDeleteContactsTool } from './delete_contacts' +export { sendGridDeleteListTool } from './delete_list' +export { sendGridDeleteTemplateTool } from './delete_template' +export { sendGridGetContactTool } from './get_contact' +export { sendGridGetListTool } from './get_list' +export { sendGridGetTemplateTool } from './get_template' +export { sendGridListAllListsTool } from './list_all_lists' +export { sendGridListTemplatesTool } from './list_templates' +export { sendGridRemoveContactsFromListTool } from './remove_contacts_from_list' +export { sendGridSearchContactsTool } from './search_contacts' +export { sendGridSendMailTool } from './send_mail' +// Types +export * from './types' diff --git a/apps/sim/tools/sendgrid/list_all_lists.ts b/apps/sim/tools/sendgrid/list_all_lists.ts new file mode 100644 index 00000000000..458091ac76d --- /dev/null +++ b/apps/sim/tools/sendgrid/list_all_lists.ts @@ -0,0 +1,58 @@ +import type { ListAllListsParams, ListsResult } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridListAllListsTool: ToolConfig = { + id: 'sendgrid_list_all_lists', + name: 'SendGrid List All Lists', + description: 'Get all contact lists from SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of lists to return per page (default: 100)', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://api.sendgrid.com/v3/marketing/lists') + if (params.pageSize) { + url.searchParams.append('page_size', params.pageSize.toString()) + } + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to list all lists') + } + + const data = await response.json() + + return { + success: true, + output: { + lists: data.result || [], + }, + } + }, + + outputs: { + lists: { type: 'json', description: 'Array of lists' }, + }, +} diff --git a/apps/sim/tools/sendgrid/list_templates.ts b/apps/sim/tools/sendgrid/list_templates.ts new file mode 100644 index 00000000000..46704478c2b --- /dev/null +++ b/apps/sim/tools/sendgrid/list_templates.ts @@ -0,0 +1,67 @@ +import type { ListTemplatesParams, TemplatesResult } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridListTemplatesTool: ToolConfig = { + id: 'sendgrid_list_templates', + name: 'SendGrid List Templates', + description: 'Get all email templates from SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + generations: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by generation (legacy, dynamic, or both)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of templates to return per page (default: 20)', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://api.sendgrid.com/v3/templates') + if (params.generations) { + url.searchParams.append('generations', params.generations) + } + if (params.pageSize) { + url.searchParams.append('page_size', params.pageSize.toString()) + } + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to list templates') + } + + const data = await response.json() + + return { + success: true, + output: { + templates: data.result || data.templates || [], + }, + } + }, + + outputs: { + templates: { type: 'json', description: 'Array of templates' }, + }, +} diff --git a/apps/sim/tools/sendgrid/remove_contacts_from_list.ts b/apps/sim/tools/sendgrid/remove_contacts_from_list.ts new file mode 100644 index 00000000000..b2251d43858 --- /dev/null +++ b/apps/sim/tools/sendgrid/remove_contacts_from_list.ts @@ -0,0 +1,67 @@ +import type { RemoveContactsFromListParams } from '@/tools/sendgrid/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const sendGridRemoveContactsFromListTool: ToolConfig< + RemoveContactsFromListParams, + ToolResponse +> = { + id: 'sendgrid_remove_contacts_from_list', + name: 'SendGrid Remove Contacts from List', + description: 'Remove contacts from a specific list in SendGrid', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + listId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'List ID', + }, + contactIds: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated contact IDs to remove from the list', + }, + }, + + request: { + url: (params) => { + const contactIds = params.contactIds + .split(',') + .map((id) => id.trim()) + .join(',') + return `https://api.sendgrid.com/v3/marketing/lists/${params.listId}/contacts?contact_ids=${encodeURIComponent(contactIds)}` + }, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to remove contacts from list') + } + + const data = await response.json() + + return { + success: true, + output: { + jobId: data.job_id, + }, + } + }, + + outputs: { + jobId: { type: 'string', description: 'Job ID for the request' }, + }, +} diff --git a/apps/sim/tools/sendgrid/search_contacts.ts b/apps/sim/tools/sendgrid/search_contacts.ts new file mode 100644 index 00000000000..d61b17bfdfe --- /dev/null +++ b/apps/sim/tools/sendgrid/search_contacts.ts @@ -0,0 +1,63 @@ +import type { ContactsResult, SearchContactsParams } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridSearchContactsTool: ToolConfig = { + id: 'sendgrid_search_contacts', + name: 'SendGrid Search Contacts', + description: 'Search for contacts in SendGrid using a query', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + query: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + "Search query (e.g., \"email LIKE '%example.com%' AND CONTAINS(list_ids, 'list-id')\")", + }, + }, + + request: { + url: () => 'https://api.sendgrid.com/v3/marketing/contacts/search', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + return { + body: JSON.stringify({ + query: params.query, + }), + } + }, + }, + + transformResponse: async (response): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to search contacts') + } + + const data = await response.json() + + return { + success: true, + output: { + contacts: data.result || [], + contactCount: data.contact_count, + }, + } + }, + + outputs: { + contacts: { type: 'json', description: 'Array of matching contacts' }, + contactCount: { type: 'number', description: 'Total number of contacts found' }, + }, +} diff --git a/apps/sim/tools/sendgrid/send_mail.ts b/apps/sim/tools/sendgrid/send_mail.ts new file mode 100644 index 00000000000..7b44a4e6873 --- /dev/null +++ b/apps/sim/tools/sendgrid/send_mail.ts @@ -0,0 +1,208 @@ +import type { SendMailParams, SendMailResult } from '@/tools/sendgrid/types' +import type { ToolConfig } from '@/tools/types' + +export const sendGridSendMailTool: ToolConfig = { + id: 'sendgrid_send_mail', + name: 'SendGrid Send Mail', + description: 'Send an email using SendGrid API', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SendGrid API key', + }, + from: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Sender email address (must be verified in SendGrid)', + }, + fromName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sender name', + }, + to: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Recipient email address', + }, + toName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Recipient name', + }, + subject: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Email subject (required unless using a template with pre-defined subject)', + }, + content: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Email body content (required unless using a template with pre-defined content)', + }, + contentType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Content type (text/plain or text/html)', + }, + cc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'CC email address', + }, + bcc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'BCC email address', + }, + replyTo: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Reply-to email address', + }, + replyToName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Reply-to name', + }, + attachments: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'JSON array of attachment objects with content (base64), filename, type, and disposition', + }, + templateId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'SendGrid template ID to use', + }, + dynamicTemplateData: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON object of dynamic template data', + }, + }, + + request: { + url: () => 'https://api.sendgrid.com/v3/mail/send', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const personalizations: any = { + to: [ + { + email: params.to, + ...(params.toName && { name: params.toName }), + }, + ], + } + + if (params.cc) { + personalizations.cc = [{ email: params.cc }] + } + + if (params.bcc) { + personalizations.bcc = [{ email: params.bcc }] + } + + if (params.templateId && params.dynamicTemplateData) { + try { + personalizations.dynamic_template_data = + typeof params.dynamicTemplateData === 'string' + ? JSON.parse(params.dynamicTemplateData) + : params.dynamicTemplateData + } catch (e) { + // If parsing fails, use as-is + } + } + + const mailBody: any = { + personalizations: [personalizations], + from: { + email: params.from, + ...(params.fromName && { name: params.fromName }), + }, + subject: params.subject, + } + + if (params.templateId) { + mailBody.template_id = params.templateId + } else { + mailBody.content = [ + { + type: params.contentType || 'text/plain', + value: params.content, + }, + ] + } + + if (params.replyTo) { + mailBody.reply_to = { + email: params.replyTo, + ...(params.replyToName && { name: params.replyToName }), + } + } + + if (params.attachments) { + try { + mailBody.attachments = + typeof params.attachments === 'string' + ? JSON.parse(params.attachments) + : params.attachments + } catch (e) { + // If parsing fails, skip attachments + } + } + + return { body: JSON.stringify(mailBody) } + }, + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.errors?.[0]?.message || 'Failed to send email') + } + + // SendGrid returns 202 Accepted with X-Message-Id header + const messageId = response.headers.get('X-Message-Id') + + return { + success: true, + output: { + success: true, + messageId: messageId || undefined, + to: params?.to || '', + subject: params?.subject || '', + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the email was sent successfully' }, + messageId: { type: 'string', description: 'SendGrid message ID' }, + to: { type: 'string', description: 'Recipient email address' }, + subject: { type: 'string', description: 'Email subject' }, + }, +} diff --git a/apps/sim/tools/sendgrid/types.ts b/apps/sim/tools/sendgrid/types.ts new file mode 100644 index 00000000000..4e187fc4bd2 --- /dev/null +++ b/apps/sim/tools/sendgrid/types.ts @@ -0,0 +1,193 @@ +import type { ToolResponse } from '@/tools/types' + +// Common types +export interface SendGridBaseParams { + apiKey: string +} + +// Mail Send types +export interface SendMailParams extends SendGridBaseParams { + from: string + fromName?: string + to: string + toName?: string + subject: string + content: string + contentType?: 'text/plain' | 'text/html' + cc?: string + bcc?: string + replyTo?: string + replyToName?: string + attachments?: string // JSON string of attachment objects + templateId?: string + dynamicTemplateData?: string // JSON string +} + +export interface SendMailResult extends ToolResponse { + output: { + success: boolean + messageId?: string + to: string + subject: string + } +} + +// Contact Management types +export interface AddContactParams extends SendGridBaseParams { + email: string + firstName?: string + lastName?: string + customFields?: string // JSON string + listIds?: string // Comma-separated list IDs +} + +export interface UpdateContactParams extends SendGridBaseParams { + contactId?: string + email: string + firstName?: string + lastName?: string + customFields?: string // JSON string + listIds?: string // Comma-separated list IDs +} + +export interface SearchContactsParams extends SendGridBaseParams { + query: string +} + +export interface GetContactParams extends SendGridBaseParams { + contactId: string +} + +export interface DeleteContactParams extends SendGridBaseParams { + contactIds: string // Comma-separated contact IDs +} + +export interface ContactResult extends ToolResponse { + output: { + id?: string + jobId?: string + email: string + firstName?: string + lastName?: string + createdAt?: string + updatedAt?: string + listIds?: string[] + customFields?: any + message?: string + } +} + +export interface ContactsResult extends ToolResponse { + output: { + contacts: any[] + contactCount?: number + } +} + +// List Management types +export interface CreateListParams extends SendGridBaseParams { + name: string +} + +export interface GetListParams extends SendGridBaseParams { + listId: string +} + +export interface UpdateListParams extends SendGridBaseParams { + listId: string + name: string +} + +export interface DeleteListParams extends SendGridBaseParams { + listId: string +} + +export interface ListAllListsParams extends SendGridBaseParams { + pageSize?: number +} + +export interface AddContactsToListParams extends SendGridBaseParams { + listId: string + contacts: string // JSON string array of contact objects with at least email +} + +export interface RemoveContactsFromListParams extends SendGridBaseParams { + listId: string + contactIds: string // Comma-separated contact IDs +} + +export interface ListResult extends ToolResponse { + output: { + id: string + name: string + contactCount?: number + } +} + +export interface ListsResult extends ToolResponse { + output: { + lists: any[] + } +} + +// Template types +export interface CreateTemplateParams extends SendGridBaseParams { + name: string + generation: 'legacy' | 'dynamic' +} + +export interface GetTemplateParams extends SendGridBaseParams { + templateId: string +} + +export interface UpdateTemplateParams extends SendGridBaseParams { + templateId: string + name: string +} + +export interface DeleteTemplateParams extends SendGridBaseParams { + templateId: string +} + +export interface ListTemplatesParams extends SendGridBaseParams { + generations?: string // 'legacy' or 'dynamic' or both + pageSize?: number +} + +export interface CreateTemplateVersionParams extends SendGridBaseParams { + templateId: string + name: string + subject: string + htmlContent?: string + plainContent?: string + active?: boolean +} + +export interface TemplateResult extends ToolResponse { + output: { + id: string + name: string + generation: string + updatedAt?: string + versions?: any[] + } +} + +export interface TemplatesResult extends ToolResponse { + output: { + templates: any[] + } +} + +export interface TemplateVersionResult extends ToolResponse { + output: { + id: string + templateId: string + name: string + subject: string + active: boolean + htmlContent?: string + plainContent?: string + updatedAt?: string + } +} From 572c9924f69da1f7ff7c7feb192ca803cbee6970 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 29 Nov 2025 13:40:54 -0800 Subject: [PATCH 2/4] feat(tools): added smtp, sendgrid, mailgun, fixed permissions in context menu --- apps/docs/components/icons.tsx | 52 ++- apps/docs/components/ui/icon-mapping.ts | 12 +- apps/docs/content/docs/en/tools/linkedin.mdx | 88 ++++ apps/docs/content/docs/en/tools/mailgun.mdx | 221 +++++++++ apps/docs/content/docs/en/tools/meta.json | 5 +- apps/docs/content/docs/en/tools/sendgrid.mdx | 28 +- apps/docs/content/docs/en/tools/smtp.mdx | 78 ++++ apps/sim/app/api/tools/smtp/send/route.ts | 211 +++++++++ .../components/oauth-required-modal.tsx | 1 + .../w/[workflowId]/components/panel/panel.tsx | 2 +- .../components/folder-item/folder-item.tsx | 5 + .../workflow-item/workflow-item.tsx | 6 + apps/sim/blocks/blocks/linkedin.ts | 112 +++++ apps/sim/blocks/blocks/mailgun.ts | 293 ++++++++++++ apps/sim/blocks/blocks/sendgrid.ts | 24 +- apps/sim/blocks/blocks/smtp.ts | 209 +++++++++ apps/sim/blocks/registry.ts | 6 + apps/sim/components/icons.tsx | 52 ++- apps/sim/lib/auth.ts | 49 ++ apps/sim/lib/env.ts | 2 + apps/sim/lib/oauth/oauth.ts | 33 ++ apps/sim/tools/linkedin/get_profile.ts | 72 +++ apps/sim/tools/linkedin/index.ts | 2 + apps/sim/tools/linkedin/share_post.ts | 163 +++++++ apps/sim/tools/linkedin/types.ts | 49 ++ apps/sim/tools/mailgun/add_list_member.ts | 101 ++++ apps/sim/tools/mailgun/create_mailing_list.ts | 99 ++++ apps/sim/tools/mailgun/get_domain.ts | 62 +++ apps/sim/tools/mailgun/get_mailing_list.ts | 61 +++ apps/sim/tools/mailgun/get_message.ts | 83 ++++ apps/sim/tools/mailgun/index.ts | 11 + apps/sim/tools/mailgun/list_domains.ts | 50 ++ apps/sim/tools/mailgun/list_messages.ts | 81 ++++ apps/sim/tools/mailgun/send_message.ts | 129 +++++ apps/sim/tools/mailgun/types.ts | 164 +++++++ apps/sim/tools/registry.ts | 23 + apps/sim/tools/sendgrid/add_contact.ts | 3 +- apps/sim/tools/sendgrid/send_mail.ts | 7 +- apps/sim/tools/sendgrid/types.ts | 9 +- apps/sim/tools/smtp/index.ts | 2 + apps/sim/tools/smtp/send_mail.ts | 177 +++++++ apps/sim/tools/smtp/types.ts | 34 ++ bun.lock | 440 ++++++++++++++++++ package.json | 2 + 44 files changed, 3282 insertions(+), 31 deletions(-) create mode 100644 apps/docs/content/docs/en/tools/linkedin.mdx create mode 100644 apps/docs/content/docs/en/tools/mailgun.mdx create mode 100644 apps/docs/content/docs/en/tools/smtp.mdx create mode 100644 apps/sim/app/api/tools/smtp/send/route.ts create mode 100644 apps/sim/blocks/blocks/linkedin.ts create mode 100644 apps/sim/blocks/blocks/mailgun.ts create mode 100644 apps/sim/blocks/blocks/smtp.ts create mode 100644 apps/sim/tools/linkedin/get_profile.ts create mode 100644 apps/sim/tools/linkedin/index.ts create mode 100644 apps/sim/tools/linkedin/share_post.ts create mode 100644 apps/sim/tools/linkedin/types.ts create mode 100644 apps/sim/tools/mailgun/add_list_member.ts create mode 100644 apps/sim/tools/mailgun/create_mailing_list.ts create mode 100644 apps/sim/tools/mailgun/get_domain.ts create mode 100644 apps/sim/tools/mailgun/get_mailing_list.ts create mode 100644 apps/sim/tools/mailgun/get_message.ts create mode 100644 apps/sim/tools/mailgun/index.ts create mode 100644 apps/sim/tools/mailgun/list_domains.ts create mode 100644 apps/sim/tools/mailgun/list_messages.ts create mode 100644 apps/sim/tools/mailgun/send_message.ts create mode 100644 apps/sim/tools/mailgun/types.ts create mode 100644 apps/sim/tools/smtp/index.ts create mode 100644 apps/sim/tools/smtp/send_mail.ts create mode 100644 apps/sim/tools/smtp/types.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 38f990a2112..c848e36eb60 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -1464,11 +1464,17 @@ export function DiscordIcon(props: SVGProps) { export function LinkedInIcon(props: SVGProps) { return ( - - + + + + + ) } @@ -4386,3 +4392,39 @@ export function SendgridIcon(props: SVGProps) { ) } + +export function MailgunIcon(props: SVGProps) { + return ( + + + + ) +} + +export function SmtpIcon(props: SVGProps) { + return ( + + + + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 3f52099d8c4..3ace0e39071 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -37,8 +37,10 @@ import { JinaAIIcon, JiraIcon, LinearIcon, + LinkedInIcon, LinkupIcon, MailchimpIcon, + MailgunIcon, Mem0Icon, MicrosoftExcelIcon, MicrosoftOneDriveIcon, @@ -64,20 +66,21 @@ import { RedditIcon, ResendIcon, S3Icon, - STTIcon, SalesforceIcon, SendgridIcon, SentryIcon, SerperIcon, SlackIcon, + SmtpIcon, + STTIcon, StagehandIcon, StripeIcon, SupabaseIcon, - TTSIcon, TavilyIcon, TelegramIcon, TranslateIcon, TrelloIcon, + TTSIcon, TwilioIcon, TypeformIcon, VideoIcon, @@ -85,10 +88,10 @@ import { WebflowIcon, WhatsAppIcon, WikipediaIcon, + xIcon, YouTubeIcon, ZendeskIcon, ZepIcon, - xIcon, } from '@/components/icons' type IconComponent = ComponentType> @@ -118,6 +121,7 @@ export const blockTypeToIconMap: Record = { stripe: StripeIcon, stagehand_agent: StagehandIcon, stagehand: StagehandIcon, + smtp: SmtpIcon, slack: SlackIcon, sharepoint: MicrosoftSharepointIcon, serper: SerperIcon, @@ -148,8 +152,10 @@ export const blockTypeToIconMap: Record = { microsoft_excel: MicrosoftExcelIcon, memory: BrainIcon, mem0: Mem0Icon, + mailgun: MailgunIcon, mailchimp: MailchimpIcon, linkup: LinkupIcon, + linkedin: LinkedInIcon, linear: LinearIcon, knowledge: PackageSearchIcon, jira: JiraIcon, diff --git a/apps/docs/content/docs/en/tools/linkedin.mdx b/apps/docs/content/docs/en/tools/linkedin.mdx new file mode 100644 index 00000000000..78d53743572 --- /dev/null +++ b/apps/docs/content/docs/en/tools/linkedin.mdx @@ -0,0 +1,88 @@ +--- +title: LinkedIn +description: Share posts and manage your LinkedIn presence +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[LinkedIn](https://www.linkedin.com) is the world’s largest professional networking platform, empowering users to build their careers, connect with their network, and share professional content. LinkedIn is widely used by professionals across industries for personal branding, recruiting, job search, and business development. + +With LinkedIn, you can easily share posts to your personal feed to engage with your network, and access information about your profile to highlight your professional achievements. Automated integration with Sim allows you to leverage LinkedIn functionality programmatically—enabling agents and workflows to post updates, report on your professional presence, and keep your feed active without manual effort. + +Key LinkedIn features available through this integration include: + +- **Share Posts:** Automatically publish professional updates, articles, or announcements to your LinkedIn personal feed. +- **Profile Information:** Retrieve detailed information about your LinkedIn profile to monitor or use in downstream tasks within your workflows. + +These capabilities make it easy to keep your LinkedIn network engaged and to extend your professional reach efficiently as part of your AI or workflow automation strategy. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate LinkedIn into workflows. Share posts to your personal feed and access your LinkedIn profile information. + + + +## Tools + +### `linkedin_share_post` + +Share a post to your personal LinkedIn feed + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `text` | string | Yes | The text content of your LinkedIn post | +| `visibility` | string | No | Who can see this post: "PUBLIC" or "CONNECTIONS" \(default: "PUBLIC"\) | +| `request` | string | No | No description | +| `output` | string | No | No description | +| `output` | string | No | No description | +| `specificContent` | string | No | No description | +| `shareCommentary` | string | No | No description | +| `visibility` | string | No | No description | +| `headers` | string | No | No description | +| `output` | string | No | No description | +| `output` | string | No | No description | +| `output` | string | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Operation success status | +| `postId` | string | Created post ID | +| `profile` | json | LinkedIn profile information | +| `error` | string | Error message if operation failed | + +### `linkedin_get_profile` + +Retrieve your LinkedIn profile information + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Operation success status | +| `postId` | string | Created post ID | +| `profile` | json | LinkedIn profile information | +| `error` | string | Error message if operation failed | + + + +## Notes + +- Category: `tools` +- Type: `linkedin` diff --git a/apps/docs/content/docs/en/tools/mailgun.mdx b/apps/docs/content/docs/en/tools/mailgun.mdx new file mode 100644 index 00000000000..450576219ae --- /dev/null +++ b/apps/docs/content/docs/en/tools/mailgun.mdx @@ -0,0 +1,221 @@ +--- +title: Mailgun +description: Send emails and manage mailing lists with Mailgun +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Mailgun](https://www.mailgun.com) is a powerful email delivery service designed for developers and businesses to send, receive, and track emails effortlessly. Mailgun enables you to leverage robust APIs for reliable transactional and marketing email, flexible mailing list management, and advanced event tracking. + +With Mailgun's comprehensive feature set, you can automate key email operations and closely monitor deliverability and recipient engagement. This makes it an ideal solution for workflow automation where communications, notifications, and campaign mails are core parts of your processes. + +Key features of Mailgun include: + +- **Transactional Email Sending:** Deliver high-volume emails such as account notifications, receipts, alerts, and password resets. +- **Rich Email Content:** Send both plain text and HTML emails, and use tags for categorizing and tracking your messages. +- **Mailing List Management:** Create, update, and manage mailing lists and members to send grouped communications efficiently. +- **Domain Information:** Retrieve details about your sending domains to monitor configuration and health. +- **Event Tracking:** Analyze email deliverability and engagement with detailed event data on sent messages. +- **Message Retrieval:** Access stored messages for compliance, analysis, or troubleshooting needs. + +By integrating Mailgun into Sim, your agents are empowered to programmatically send emails, manage email lists, access domain information, and monitor real-time events as part of automated workflows. This allows for intelligent, data-driven engagement with your users directly from your AI-powered processes. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Mailgun into your workflow. Send transactional emails, manage mailing lists and members, view domain information, and track email events. Supports text and HTML emails, tags for tracking, and comprehensive list management. + + + +## Tools + +### `mailgun_send_message` + +Send an email using Mailgun API + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Mailgun API key | +| `domain` | string | Yes | Mailgun domain \(e.g., mg.example.com\) | +| `from` | string | Yes | Sender email address | +| `to` | string | Yes | Recipient email address \(comma-separated for multiple\) | +| `subject` | string | Yes | Email subject | +| `text` | string | No | Plain text body of the email | +| `html` | string | No | HTML body of the email | +| `cc` | string | No | CC email address \(comma-separated for multiple\) | +| `bcc` | string | No | BCC email address \(comma-separated for multiple\) | +| `tags` | string | No | Tags for the email \(comma-separated\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the message was sent successfully | +| `id` | string | Message ID | +| `message` | string | Response message from Mailgun | + +### `mailgun_get_message` + +Retrieve a stored message by its key + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Mailgun API key | +| `domain` | string | Yes | Mailgun domain | +| `messageKey` | string | Yes | Message storage key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the request was successful | +| `recipients` | string | Message recipients | +| `from` | string | Sender email | +| `subject` | string | Message subject | +| `bodyPlain` | string | Plain text body | +| `strippedText` | string | Stripped text | +| `strippedSignature` | string | Stripped signature | +| `bodyHtml` | string | HTML body | +| `strippedHtml` | string | Stripped HTML | +| `attachmentCount` | number | Number of attachments | +| `timestamp` | number | Message timestamp | +| `messageHeaders` | json | Message headers | +| `contentIdMap` | json | Content ID map | + +### `mailgun_list_messages` + +List events (logs) for messages sent through Mailgun + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Mailgun API key | +| `domain` | string | Yes | Mailgun domain | +| `event` | string | No | Filter by event type \(accepted, delivered, failed, opened, clicked, etc.\) | +| `limit` | number | No | Maximum number of events to return \(default: 100\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the request was successful | +| `items` | json | Array of event items | +| `paging` | json | Paging information | + +### `mailgun_create_mailing_list` + +Create a new mailing list + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Mailgun API key | +| `address` | string | Yes | Mailing list address \(e.g., list@example.com\) | +| `name` | string | No | Mailing list name | +| `description` | string | No | Mailing list description | +| `accessLevel` | string | No | Access level: readonly, members, or everyone | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the list was created successfully | +| `message` | string | Response message | +| `list` | json | Created mailing list details | + +### `mailgun_get_mailing_list` + +Get details of a mailing list + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Mailgun API key | +| `address` | string | Yes | Mailing list address | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the request was successful | +| `list` | json | Mailing list details | + +### `mailgun_add_list_member` + +Add a member to a mailing list + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Mailgun API key | +| `listAddress` | string | Yes | Mailing list address | +| `address` | string | Yes | Member email address | +| `name` | string | No | Member name | +| `vars` | string | No | JSON string of custom variables | +| `subscribed` | boolean | No | Whether the member is subscribed | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the member was added successfully | +| `message` | string | Response message | +| `member` | json | Added member details | + +### `mailgun_list_domains` + +List all domains for your Mailgun account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Mailgun API key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the request was successful | +| `totalCount` | number | Total number of domains | +| `items` | json | Array of domain objects | + +### `mailgun_get_domain` + +Get details of a specific domain + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Mailgun API key | +| `domain` | string | Yes | Domain name | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the request was successful | +| `domain` | json | Domain details | + + + +## Notes + +- Category: `tools` +- Type: `mailgun` diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 8cd77e75698..e8a4b7478a9 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -33,8 +33,10 @@ "jira", "knowledge", "linear", + "linkedin", "linkup", "mailchimp", + "mailgun", "mem0", "memory", "microsoft_excel", @@ -65,6 +67,7 @@ "serper", "sharepoint", "slack", + "smtp", "stagehand", "stagehand_agent", "stripe", @@ -90,4 +93,4 @@ "zendesk", "zep" ] -} \ No newline at end of file +} diff --git a/apps/docs/content/docs/en/tools/sendgrid.mdx b/apps/docs/content/docs/en/tools/sendgrid.mdx index 0b9c8ae65e0..6bff00fae38 100644 --- a/apps/docs/content/docs/en/tools/sendgrid.mdx +++ b/apps/docs/content/docs/en/tools/sendgrid.mdx @@ -10,6 +10,30 @@ import { BlockInfoCard } from "@/components/ui/block-info-card" color="#1A82E2" /> +{/* MANUAL-CONTENT-START:intro */} +[SendGrid](https://sendgrid.com) is a leading cloud-based email delivery platform trusted by developers and businesses to send reliable transactional and marketing emails at scale. With its robust APIs and powerful tools, SendGrid enables you to manage all aspects of your email communication, from sending notifications and receipts to managing complex marketing campaigns. + +SendGrid empowers users with a full suite of email operations, allowing you to automate critical email workflows and closely manage contact lists, templates, and recipient engagement. Its seamless integration with Sim enables agents and workflows to deliver targeted messages, maintain dynamic contact and recipient lists, trigger personalized emails through templates, and track the results in real time. + +Key features of SendGrid include: + +- **Transactional Email:** Send automated and high-volume transactional emails (like notifications, receipts, and password resets). +- **Dynamic Templates:** Use rich HTML or text templates with dynamic data for highly personalized communication at scale. +- **Contact Management:** Add and update marketing contacts, manage recipient lists, and target segments for campaigns. +- **Attachments Support:** Include one or more file attachments in your emails. +- **Comprehensive API Coverage:** Programmatically manage emails, contacts, lists, templates, suppression groups, and more. + +By connecting SendGrid with Sim, your agents can: + +- Send both simple and advanced (templated or multi-recipient) emails as part of any workflow. +- Manage and segment contacts and lists automatically. +- Leverage templates for consistency and dynamic personalization. +- Track and respond to email engagement within your automated processes. + +This integration allows you to automate all critical communication flows, ensure messages reach the right audience, and maintain control over your organization’s email strategy, directly from Sim workflows. +{/* MANUAL-CONTENT-END */} + + ## Usage Instructions Integrate SendGrid into your workflow. Send transactional emails, manage marketing contacts and lists, and work with email templates. Supports dynamic templates, attachments, and comprehensive contact management. @@ -38,7 +62,7 @@ Send an email using SendGrid API | `bcc` | string | No | BCC email address | | `replyTo` | string | No | Reply-to email address | | `replyToName` | string | No | Reply-to name | -| `attachments` | json | No | JSON array of attachment objects with content \(base64\), filename, type, and disposition | +| `attachments` | file[] | No | Files to attach to the email | | `templateId` | string | No | SendGrid template ID to use | | `dynamicTemplateData` | json | No | JSON object of dynamic template data | @@ -63,7 +87,7 @@ Add a new contact to SendGrid | `email` | string | Yes | Contact email address | | `firstName` | string | No | Contact first name | | `lastName` | string | No | Contact last name | -| `customFields` | json | No | JSON object of custom field key-value pairs | +| `customFields` | json | No | JSON object of custom field key-value pairs \(use field IDs like e1_T, e2_N, e3_D, not field names\) | | `listIds` | string | No | Comma-separated list IDs to add the contact to | #### Output diff --git a/apps/docs/content/docs/en/tools/smtp.mdx b/apps/docs/content/docs/en/tools/smtp.mdx new file mode 100644 index 00000000000..5c7197fc55d --- /dev/null +++ b/apps/docs/content/docs/en/tools/smtp.mdx @@ -0,0 +1,78 @@ +--- +title: SMTP +description: Send emails via any SMTP mail server +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[SMTP (Simple Mail Transfer Protocol)](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) is the foundational standard for email transmission across the Internet. By connecting to any SMTP-compatible server—such as Gmail, Outlook, or your organization's own mail infrastructure—you can send emails programmatically and automate your outbound communications. + +SMTP integration allows you to fully customize email sending through direct server connectivity, supporting both basic and advanced email use cases. With SMTP, you can control every aspect of message delivery, recipient management, and content formatting, making it suitable for transactional notifications, bulk mailings, and any automated workflow requiring robust outbound email delivery. + +**Key features available via SMTP integration include:** + +- **Universal Email Delivery:** Send emails using any SMTP server by configuring standard server connection parameters. +- **Customizable Sender and Recipients:** Specify sender address, display name, primary recipients, as well as CC and BCC fields. +- **Rich Content Support:** Send plain text or richly formatted HTML emails according to your requirements. +- **Attachments:** Include multiple files as attachments in outgoing emails. +- **Flexible Security:** Connect using TLS, SSL, or standard (unencrypted) protocols as supported by your SMTP provider. +- **Advanced Headers:** Set reply-to headers and other advanced email options to cater for complex mailflows and user interactions. + +By integrating SMTP with Sim, agents and workflows can programmatically send emails as part of any automated process—ranging from sending notifications and confirmations, to automating external communications, reporting, and document delivery. This offers a highly flexible, provider-agnostic approach to managing email directly within your AI-driven processes. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Send emails using any SMTP server (Gmail, Outlook, custom servers, etc.). Configure SMTP connection settings and send emails with full control over content, recipients, and attachments. + + + +## Tools + +### `smtp_send_mail` + +Send emails via SMTP server + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `smtpHost` | string | Yes | SMTP server hostname \(e.g., smtp.gmail.com\) | +| `smtpPort` | number | Yes | SMTP server port \(587 for TLS, 465 for SSL\) | +| `smtpUsername` | string | Yes | SMTP authentication username | +| `smtpPassword` | string | Yes | SMTP authentication password | +| `smtpSecure` | string | Yes | Security protocol \(TLS, SSL, or None\) | +| `from` | string | Yes | Sender email address | +| `to` | string | Yes | Recipient email address | +| `subject` | string | Yes | Email subject | +| `body` | string | Yes | Email body content | +| `contentType` | string | No | Content type \(text or html\) | +| `fromName` | string | No | Display name for sender | +| `cc` | string | No | CC recipients \(comma-separated\) | +| `bcc` | string | No | BCC recipients \(comma-separated\) | +| `replyTo` | string | No | Reply-to email address | +| `attachments` | file[] | No | Files to attach to the email | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `success` | boolean | Whether the email was sent successfully | +| `messageId` | string | Message ID from SMTP server | +| `to` | string | Recipient email address | +| `subject` | string | Email subject | +| `error` | string | Error message if sending failed | + + + +## Notes + +- Category: `tools` +- Type: `smtp` diff --git a/apps/sim/app/api/tools/smtp/send/route.ts b/apps/sim/app/api/tools/smtp/send/route.ts new file mode 100644 index 00000000000..d9e02f783ea --- /dev/null +++ b/apps/sim/app/api/tools/smtp/send/route.ts @@ -0,0 +1,211 @@ +import { type NextRequest, NextResponse } from 'next/server' +import nodemailer from 'nodemailer' +import { z } from 'zod' +import { checkHybridAuth } from '@/lib/auth/hybrid' +import { createLogger } from '@/lib/logs/console/logger' +import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' +import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' +import { generateRequestId } from '@/lib/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('SmtpSendAPI') + +const SmtpSendSchema = z.object({ + smtpHost: z.string().min(1, 'SMTP host is required'), + smtpPort: z.number().min(1).max(65535, 'Port must be between 1 and 65535'), + smtpUsername: z.string().min(1, 'SMTP username is required'), + smtpPassword: z.string().min(1, 'SMTP password is required'), + smtpSecure: z.enum(['TLS', 'SSL', 'None']), + + from: z.string().email('Invalid from email address').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(), + + fromName: z.string().optional().nullable(), + cc: z.string().optional().nullable(), + bcc: z.string().optional().nullable(), + replyTo: z.string().optional().nullable(), + attachments: z.array(z.any()).optional().nullable(), +}) + +export async function POST(request: NextRequest) { + const requestId = generateRequestId() + + try { + const authResult = await checkHybridAuth(request, { requireWorkflowId: false }) + + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized SMTP send attempt: ${authResult.error}`) + return NextResponse.json( + { + success: false, + error: authResult.error || 'Authentication required', + }, + { status: 401 } + ) + } + + logger.info(`[${requestId}] Authenticated SMTP request via ${authResult.authType}`, { + userId: authResult.userId, + }) + + const body = await request.json() + const validatedData = SmtpSendSchema.parse(body) + + logger.info(`[${requestId}] Sending email via SMTP`, { + host: validatedData.smtpHost, + port: validatedData.smtpPort, + to: validatedData.to, + subject: validatedData.subject, + secure: validatedData.smtpSecure, + }) + + const transporter = nodemailer.createTransport({ + host: validatedData.smtpHost, + port: validatedData.smtpPort, + secure: validatedData.smtpSecure === 'SSL', + auth: { + user: validatedData.smtpUsername, + pass: validatedData.smtpPassword, + }, + tls: + validatedData.smtpSecure === 'None' + ? { + rejectUnauthorized: false, + } + : { + rejectUnauthorized: true, + }, + }) + + const contentType = validatedData.contentType || 'text' + const fromAddress = validatedData.fromName + ? `"${validatedData.fromName}" <${validatedData.from}>` + : validatedData.from + + const mailOptions: any = { + from: fromAddress, + to: validatedData.to, + subject: validatedData.subject, + [contentType === 'html' ? 'html' : 'text']: validatedData.body, + } + + if (validatedData.cc) { + mailOptions.cc = validatedData.cc + } + if (validatedData.bcc) { + mailOptions.bcc = validatedData.bcc + } + if (validatedData.replyTo) { + mailOptions.replyTo = validatedData.replyTo + } + + if (validatedData.attachments && validatedData.attachments.length > 0) { + const rawAttachments = validatedData.attachments + logger.info(`[${requestId}] Processing ${rawAttachments.length} attachment(s)`) + + const attachments = processFilesToUserFiles(rawAttachments, requestId, logger) + + if (attachments.length > 0) { + const totalSize = attachments.reduce((sum, file) => sum + file.size, 0) + const maxSize = 25 * 1024 * 1024 + + if (totalSize > maxSize) { + const sizeMB = (totalSize / (1024 * 1024)).toFixed(2) + return NextResponse.json( + { + success: false, + error: `Total attachment size (${sizeMB}MB) exceeds SMTP limit of 25MB`, + }, + { status: 400 } + ) + } + + const attachmentBuffers = await Promise.all( + attachments.map(async (file) => { + try { + logger.info( + `[${requestId}] Downloading attachment: ${file.name} (${file.size} bytes)` + ) + + const buffer = await downloadFileFromStorage(file, requestId, logger) + + return { + filename: file.name, + content: buffer, + contentType: file.type || 'application/octet-stream', + } + } catch (error) { + logger.error(`[${requestId}] Failed to download attachment ${file.name}:`, error) + throw new Error( + `Failed to download attachment "${file.name}": ${error instanceof Error ? error.message : 'Unknown error'}` + ) + } + }) + ) + + logger.info(`[${requestId}] Processed ${attachmentBuffers.length} attachment(s)`) + mailOptions.attachments = attachmentBuffers + } + } + + const result = await transporter.sendMail(mailOptions) + + logger.info(`[${requestId}] Email sent successfully via SMTP`, { + messageId: result.messageId, + to: validatedData.to, + }) + + return NextResponse.json({ + success: true, + messageId: result.messageId, + to: validatedData.to, + subject: validatedData.subject, + }) + } catch (error: any) { + if (error instanceof z.ZodError) { + logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) + return NextResponse.json( + { + success: false, + error: 'Invalid request data', + details: error.errors, + }, + { status: 400 } + ) + } + + let errorMessage = 'Failed to send email via SMTP' + + if (error.code === 'EAUTH') { + errorMessage = 'SMTP authentication failed - check username and password' + } else if (error.code === 'ECONNECTION' || error.code === 'ECONNREFUSED') { + errorMessage = 'Could not connect to SMTP server - check host and port' + } else if (error.code === 'ECONNRESET') { + errorMessage = 'Connection was reset by SMTP server - check network or firewall settings' + } else if (error.code === 'ETIMEDOUT') { + errorMessage = 'SMTP server connection timeout - check host, port, and network' + } else if (error.responseCode && error.responseCode >= 500) { + errorMessage = 'SMTP server error - please try again later' + } else if (error.responseCode && error.responseCode >= 400) { + errorMessage = 'Email rejected by SMTP server - check recipient addresses' + } + + logger.error(`[${requestId}] Error sending email via SMTP:`, { + error: error.message, + code: error.code, + responseCode: error.responseCode, + }) + + return NextResponse.json( + { + success: false, + error: errorMessage, + }, + { status: 500 } + ) + } +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx index 34530ab9c03..21b3892423a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx @@ -231,6 +231,7 @@ const SCOPE_DESCRIPTIONS: Record = { 'projects:full': 'Full access to manage your Pipedrive projects', 'webhooks:read': 'Read your Pipedrive webhooks', 'webhooks:full': 'Full access to manage your Pipedrive webhooks', + w_member_social: 'Access your LinkedIn profile', } function getScopeDescription(scope: string): string { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx index 8f2ed8bea05..bee0688e437 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx @@ -411,7 +411,7 @@ export function Panel() { */} Export workflow diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/folder-item/folder-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/folder-item/folder-item.tsx index 1cf02a14533..3eedbf40597 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/folder-item/folder-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/folder-item/folder-item.tsx @@ -4,6 +4,7 @@ import { useCallback, useState } from 'react' import clsx from 'clsx' import { ChevronRight, Folder, FolderOpen } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' +import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { ContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/context-menu/context-menu' import { DeleteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/delete-modal/delete-modal' import { @@ -40,6 +41,7 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) { const workspaceId = params.workspaceId as string const updateFolderMutation = useUpdateFolder() const createWorkflowMutation = useCreateWorkflow() + const userPermissions = useUserPermissionsContext() // Delete modal state const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) @@ -260,6 +262,9 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) { onDuplicate={handleDuplicateFolder} onDelete={() => setIsDeleteModalOpen(true)} showCreate={true} + disableRename={!userPermissions.canEdit} + disableDuplicate={!userPermissions.canEdit} + disableDelete={!userPermissions.canEdit} /> {/* Delete Modal */} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/workflow-item/workflow-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/workflow-item/workflow-item.tsx index f5590337579..5e272d8e64d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/workflow-item/workflow-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/workflow-item/workflow-item.tsx @@ -4,6 +4,7 @@ import { useCallback, useRef, useState } from 'react' import clsx from 'clsx' import Link from 'next/link' import { useParams } from 'next/navigation' +import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { ContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/context-menu/context-menu' import { DeleteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/delete-modal/delete-modal' import { Avatars } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/workflow-list/components/workflow-item/avatars/avatars' @@ -40,6 +41,7 @@ export function WorkflowItem({ workflow, active, level, onWorkflowClick }: Workf const workspaceId = params.workspaceId as string const { selectedWorkflows } = useFolderStore() const { updateWorkflow, workflows } = useWorkflowRegistry() + const userPermissions = useUserPermissionsContext() const isSelected = selectedWorkflows.has(workflow.id) // Delete modal state @@ -309,6 +311,10 @@ export function WorkflowItem({ workflow, active, level, onWorkflowClick }: Workf showRename={selectedWorkflows.size <= 1} showDuplicate={true} showExport={true} + disableRename={!userPermissions.canEdit} + disableDuplicate={!userPermissions.canEdit} + disableExport={!userPermissions.canEdit} + disableDelete={!userPermissions.canEdit} /> {/* Delete Confirmation Modal */} diff --git a/apps/sim/blocks/blocks/linkedin.ts b/apps/sim/blocks/blocks/linkedin.ts new file mode 100644 index 00000000000..9d9e3208d18 --- /dev/null +++ b/apps/sim/blocks/blocks/linkedin.ts @@ -0,0 +1,112 @@ +import { LinkedInIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' +import type { LinkedInResponse } from '@/tools/linkedin/types' + +export const LinkedInBlock: BlockConfig = { + type: 'linkedin', + name: 'LinkedIn', + description: 'Share posts and manage your LinkedIn presence', + authMode: AuthMode.OAuth, + longDescription: + 'Integrate LinkedIn into workflows. Share posts to your personal feed and access your LinkedIn profile information.', + docsLink: 'https://docs.sim.ai/tools/linkedin', + category: 'tools', + bgColor: '#0072B1', + icon: LinkedInIcon, + subBlocks: [ + // Operation selection + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Share Post', id: 'share_post' }, + { label: 'Get Profile', id: 'get_profile' }, + ], + value: () => 'share_post', + }, + + // LinkedIn OAuth Authentication + { + id: 'credential', + title: 'LinkedIn Account', + type: 'oauth-input', + provider: 'linkedin', + serviceId: 'linkedin', + requiredScopes: ['profile', 'openid', 'email', 'w_member_social'], + placeholder: 'Select LinkedIn account', + required: true, + }, + + // Share Post specific fields + { + id: 'text', + title: 'Post Text', + type: 'long-input', + placeholder: 'What do you want to share on LinkedIn?', + condition: { + field: 'operation', + value: 'share_post', + }, + required: true, + }, + { + id: 'visibility', + title: 'Visibility', + type: 'dropdown', + options: [ + { label: 'Public', id: 'PUBLIC' }, + { label: 'Connections Only', id: 'CONNECTIONS' }, + ], + condition: { + field: 'operation', + value: 'share_post', + }, + value: () => 'PUBLIC', + required: true, + }, + ], + tools: { + access: ['linkedin_share_post', 'linkedin_get_profile'], + config: { + tool: (inputs) => { + const operation = inputs.operation || 'share_post' + + if (operation === 'get_profile') { + return 'linkedin_get_profile' + } + + return 'linkedin_share_post' + }, + params: (inputs) => { + const operation = inputs.operation || 'share_post' + const { credential, ...rest } = inputs + + if (operation === 'get_profile') { + return { + accessToken: credential, + } + } + + return { + text: rest.text, + visibility: rest.visibility || 'PUBLIC', + accessToken: credential, + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + credential: { type: 'string', description: 'LinkedIn access token' }, + text: { type: 'string', description: 'Post text content' }, + visibility: { type: 'string', description: 'Post visibility (PUBLIC or CONNECTIONS)' }, + }, + outputs: { + success: { type: 'boolean', description: 'Operation success status' }, + postId: { type: 'string', description: 'Created post ID' }, + profile: { type: 'json', description: 'LinkedIn profile information' }, + error: { type: 'string', description: 'Error message if operation failed' }, + }, +} diff --git a/apps/sim/blocks/blocks/mailgun.ts b/apps/sim/blocks/blocks/mailgun.ts new file mode 100644 index 00000000000..1fdeb3b6290 --- /dev/null +++ b/apps/sim/blocks/blocks/mailgun.ts @@ -0,0 +1,293 @@ +import { MailgunIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import type { SendMessageResult } from '@/tools/mailgun/types' + +export const MailgunBlock: BlockConfig = { + type: 'mailgun', + name: 'Mailgun', + description: 'Send emails and manage mailing lists with Mailgun', + longDescription: + 'Integrate Mailgun into your workflow. Send transactional emails, manage mailing lists and members, view domain information, and track email events. Supports text and HTML emails, tags for tracking, and comprehensive list management.', + docsLink: 'https://docs.sim.ai/tools/mailgun', + category: 'tools', + bgColor: '#F06248', + icon: MailgunIcon, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + // Message Operations + { label: 'Send Message', id: 'send_message' }, + { label: 'Get Message', id: 'get_message' }, + { label: 'List Messages', id: 'list_messages' }, + // Mailing List Operations + { label: 'Create Mailing List', id: 'create_mailing_list' }, + { label: 'Get Mailing List', id: 'get_mailing_list' }, + { label: 'Add List Member', id: 'add_list_member' }, + // Domain Operations + { label: 'List Domains', id: 'list_domains' }, + { label: 'Get Domain', id: 'get_domain' }, + ], + value: () => 'send_message', + }, + { + id: 'apiKey', + title: 'Mailgun API Key', + type: 'short-input', + password: true, + placeholder: 'Enter your Mailgun API key', + required: true, + }, + { + id: 'domain', + title: 'Domain', + type: 'short-input', + placeholder: 'mg.example.com', + condition: { + field: 'operation', + value: ['send_message', 'get_message', 'list_messages', 'get_domain'], + }, + required: true, + }, + // Send Message fields + { + id: 'from', + title: 'From Email', + type: 'short-input', + placeholder: 'sender@example.com', + condition: { field: 'operation', value: 'send_message' }, + required: true, + }, + { + id: 'to', + title: 'To Email', + type: 'short-input', + placeholder: 'recipient@example.com', + condition: { field: 'operation', value: 'send_message' }, + required: true, + }, + { + id: 'subject', + title: 'Subject', + type: 'short-input', + placeholder: 'Email subject', + condition: { field: 'operation', value: 'send_message' }, + required: true, + }, + { + id: 'text', + title: 'Text Body', + type: 'long-input', + placeholder: 'Plain text email body', + condition: { field: 'operation', value: 'send_message' }, + }, + { + id: 'html', + title: 'HTML Body', + type: 'code', + placeholder: 'HTML email body', + condition: { field: 'operation', value: 'send_message' }, + }, + { + id: 'cc', + title: 'CC', + type: 'short-input', + placeholder: 'cc@example.com', + condition: { field: 'operation', value: 'send_message' }, + }, + { + id: 'bcc', + title: 'BCC', + type: 'short-input', + placeholder: 'bcc@example.com', + condition: { field: 'operation', value: 'send_message' }, + }, + { + id: 'tags', + title: 'Tags', + type: 'short-input', + placeholder: 'tag1, tag2', + condition: { field: 'operation', value: 'send_message' }, + }, + // Get Message fields + { + id: 'messageKey', + title: 'Message Key', + type: 'short-input', + placeholder: 'Message storage key', + condition: { field: 'operation', value: 'get_message' }, + required: true, + }, + // List Messages fields + { + id: 'event', + title: 'Event Type', + type: 'dropdown', + options: [ + { label: 'All Events', id: '' }, + { label: 'Accepted', id: 'accepted' }, + { label: 'Delivered', id: 'delivered' }, + { label: 'Failed', id: 'failed' }, + { label: 'Opened', id: 'opened' }, + { label: 'Clicked', id: 'clicked' }, + { label: 'Unsubscribed', id: 'unsubscribed' }, + { label: 'Complained', id: 'complained' }, + { label: 'Stored', id: 'stored' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_messages' }, + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: '100', + condition: { field: 'operation', value: 'list_messages' }, + }, + // Create Mailing List fields + { + id: 'address', + title: 'List Address', + type: 'short-input', + placeholder: 'list@example.com', + condition: { + field: 'operation', + value: ['create_mailing_list', 'get_mailing_list', 'add_list_member'], + }, + required: true, + }, + { + id: 'name', + title: 'List Name', + type: 'short-input', + placeholder: 'My Mailing List', + condition: { field: 'operation', value: 'create_mailing_list' }, + }, + { + id: 'description', + title: 'Description', + type: 'long-input', + placeholder: 'Description of the mailing list', + condition: { field: 'operation', value: 'create_mailing_list' }, + }, + { + id: 'accessLevel', + title: 'Access Level', + type: 'dropdown', + options: [ + { label: 'Read Only', id: 'readonly' }, + { label: 'Members', id: 'members' }, + { label: 'Everyone', id: 'everyone' }, + ], + value: () => 'readonly', + condition: { field: 'operation', value: 'create_mailing_list' }, + }, + // Add List Member fields (reuse address from above for listAddress) + { + id: 'memberAddress', + title: 'Member Email', + type: 'short-input', + placeholder: 'member@example.com', + condition: { field: 'operation', value: 'add_list_member' }, + required: true, + }, + { + id: 'memberName', + title: 'Member Name', + type: 'short-input', + placeholder: 'John Doe', + condition: { field: 'operation', value: 'add_list_member' }, + }, + { + id: 'vars', + title: 'Custom Variables', + type: 'code', + placeholder: '{"key": "value"}', + condition: { field: 'operation', value: 'add_list_member' }, + }, + { + id: 'subscribed', + title: 'Subscribed', + type: 'dropdown', + options: [ + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'true', + condition: { field: 'operation', value: 'add_list_member' }, + }, + ], + + tools: { + access: [ + 'mailgun_send_message', + 'mailgun_get_message', + 'mailgun_list_messages', + 'mailgun_create_mailing_list', + 'mailgun_get_mailing_list', + 'mailgun_add_list_member', + 'mailgun_list_domains', + 'mailgun_get_domain', + ], + config: { + tool: (params) => `mailgun_${params.operation}`, + params: (params) => { + const { operation, memberAddress, memberName, ...rest } = params + + // Handle special field mappings for add_list_member + if (operation === 'add_list_member') { + return { + ...rest, + listAddress: params.address, + address: memberAddress, + name: memberName, + subscribed: params.subscribed === 'true', + } + } + + return rest + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Mailgun API key' }, + domain: { type: 'string', description: 'Mailgun domain' }, + // Message inputs + from: { type: 'string', description: 'Sender email address' }, + to: { type: 'string', description: 'Recipient email address' }, + subject: { type: 'string', description: 'Email subject' }, + text: { type: 'string', description: 'Plain text body' }, + html: { type: 'string', description: 'HTML body' }, + cc: { type: 'string', description: 'CC email address' }, + bcc: { type: 'string', description: 'BCC email address' }, + tags: { type: 'string', description: 'Tags for the email' }, + messageKey: { type: 'string', description: 'Message storage key' }, + event: { type: 'string', description: 'Event type filter' }, + limit: { type: 'number', description: 'Number of events to return' }, + // Mailing list inputs + address: { type: 'string', description: 'Mailing list address' }, + name: { type: 'string', description: 'List or member name' }, + description: { type: 'string', description: 'List description' }, + accessLevel: { type: 'string', description: 'List access level' }, + memberAddress: { type: 'string', description: 'Member email address' }, + memberName: { type: 'string', description: 'Member name' }, + vars: { type: 'string', description: 'Custom variables JSON' }, + subscribed: { type: 'string', description: 'Member subscription status' }, + }, + + outputs: { + success: { type: 'boolean', description: 'Operation success status' }, + id: { type: 'string', description: 'Message ID' }, + message: { type: 'string', description: 'Response message' }, + items: { type: 'json', description: 'Array of items (messages, domains)' }, + list: { type: 'json', description: 'Mailing list details' }, + member: { type: 'json', description: 'Member details' }, + domain: { type: 'json', description: 'Domain details' }, + totalCount: { type: 'number', description: 'Total count of items' }, + }, +} diff --git a/apps/sim/blocks/blocks/sendgrid.ts b/apps/sim/blocks/blocks/sendgrid.ts index 4f083a7abc0..130ef13156d 100644 --- a/apps/sim/blocks/blocks/sendgrid.ts +++ b/apps/sim/blocks/blocks/sendgrid.ts @@ -148,13 +148,28 @@ export const SendGridBlock: BlockConfig = { placeholder: '{"name": "John", "order_id": "12345"}', condition: { field: 'operation', value: 'send_mail' }, }, + // File upload (basic mode) + { + id: 'attachmentFiles', + title: 'Attachments', + type: 'file-upload', + canonicalParamId: 'attachments', + placeholder: 'Upload files to attach', + condition: { field: 'operation', value: 'send_mail' }, + mode: 'basic', + multiple: true, + required: false, + }, + // Variable reference (advanced mode) { id: 'attachments', title: 'Attachments', - type: 'code', - placeholder: - '[{"content": "base64string", "filename": "file.pdf", "type": "application/pdf"}]', + type: 'short-input', + canonicalParamId: 'attachments', + placeholder: 'Reference files from previous blocks', condition: { field: 'operation', value: 'send_mail' }, + mode: 'advanced', + required: false, }, // Contact fields { @@ -415,7 +430,8 @@ export const SendGridBlock: BlockConfig = { replyToName: { type: 'string', description: 'Reply-to name' }, mailTemplateId: { type: 'string', description: 'Template ID for sending mail' }, dynamicTemplateData: { type: 'json', description: 'Dynamic template data' }, - attachments: { type: 'json', description: 'Email attachments' }, + attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' }, + attachments: { type: 'array', description: 'Files to attach (UserFile array)' }, // Contact inputs email: { type: 'string', description: 'Contact email' }, firstName: { type: 'string', description: 'Contact first name' }, diff --git a/apps/sim/blocks/blocks/smtp.ts b/apps/sim/blocks/blocks/smtp.ts new file mode 100644 index 00000000000..d6ed2360597 --- /dev/null +++ b/apps/sim/blocks/blocks/smtp.ts @@ -0,0 +1,209 @@ +import { SmtpIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' +import type { SmtpSendMailResult } from '@/tools/smtp/types' + +export const SmtpBlock: BlockConfig = { + type: 'smtp', + name: 'SMTP', + description: 'Send emails via any SMTP mail server', + longDescription: + 'Send emails using any SMTP server (Gmail, Outlook, custom servers, etc.). Configure SMTP connection settings and send emails with full control over content, recipients, and attachments.', + docsLink: 'https://docs.sim.ai/tools/smtp', + category: 'tools', + bgColor: '#4A5568', + icon: SmtpIcon, + authMode: AuthMode.ApiKey, + + subBlocks: [ + { + id: 'smtpHost', + title: 'SMTP Host', + type: 'short-input', + placeholder: 'smtp.gmail.com, smtp.example.com', + required: true, + }, + { + id: 'smtpPort', + title: 'SMTP Port', + type: 'short-input', + placeholder: '587', + required: true, + value: () => '587', + }, + { + id: 'smtpUsername', + title: 'SMTP Username', + type: 'short-input', + placeholder: 'your-email@example.com', + required: true, + }, + { + id: 'smtpPassword', + title: 'SMTP Password', + type: 'short-input', + placeholder: 'Your SMTP password', + required: true, + password: true, + }, + { + id: 'smtpSecure', + title: 'Security Mode', + type: 'dropdown', + options: [ + { label: 'TLS (Port 587)', id: 'TLS' }, + { label: 'SSL (Port 465)', id: 'SSL' }, + { label: 'None (Port 25)', id: 'None' }, + ], + value: () => 'TLS', + required: true, + }, + + { + id: 'from', + title: 'From', + type: 'short-input', + placeholder: 'sender@example.com', + required: true, + }, + { + id: 'to', + title: 'To', + type: 'short-input', + placeholder: 'recipient@example.com', + required: true, + }, + { + id: 'subject', + title: 'Subject', + type: 'short-input', + placeholder: 'Email subject', + required: true, + }, + { + id: 'body', + title: 'Body', + type: 'long-input', + placeholder: 'Email content', + required: true, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'Plain Text', id: 'text' }, + { label: 'HTML', id: 'html' }, + ], + value: () => 'text', + required: false, + }, + + // Attachments Section + // File upload (basic mode) + { + id: 'attachmentFiles', + title: 'Attachments', + type: 'file-upload', + canonicalParamId: 'attachments', + placeholder: 'Upload files to attach', + mode: 'basic', + multiple: true, + required: false, + }, + // Variable reference (advanced mode) + { + id: 'attachments', + title: 'Attachments', + type: 'short-input', + canonicalParamId: 'attachments', + placeholder: 'Reference files from previous blocks', + mode: 'advanced', + required: false, + }, + + // Advanced Options Section + { + id: 'fromName', + title: 'From Name', + type: 'short-input', + placeholder: 'Display name for sender', + mode: 'advanced', + required: false, + }, + { + id: 'cc', + title: 'CC', + type: 'short-input', + placeholder: 'cc1@example.com, cc2@example.com', + mode: 'advanced', + required: false, + }, + { + id: 'bcc', + title: 'BCC', + type: 'short-input', + placeholder: 'bcc1@example.com, bcc2@example.com', + mode: 'advanced', + required: false, + }, + { + id: 'replyTo', + title: 'Reply To', + type: 'short-input', + placeholder: 'reply@example.com', + mode: 'advanced', + required: false, + }, + ], + + tools: { + access: ['smtp_send_mail'], + config: { + tool: () => 'smtp_send_mail', + params: (params) => ({ + smtpHost: params.smtpHost, + smtpPort: Number(params.smtpPort), + smtpUsername: params.smtpUsername, + smtpPassword: params.smtpPassword, + smtpSecure: params.smtpSecure, + from: params.from, + to: params.to, + subject: params.subject, + body: params.body, + contentType: params.contentType, + fromName: params.fromName, + cc: params.cc, + bcc: params.bcc, + replyTo: params.replyTo, + attachments: params.attachments, + }), + }, + }, + + inputs: { + smtpHost: { type: 'string', description: 'SMTP server hostname' }, + smtpPort: { type: 'number', description: 'SMTP server port' }, + smtpUsername: { type: 'string', description: 'SMTP authentication username' }, + smtpPassword: { type: 'string', description: 'SMTP authentication password' }, + smtpSecure: { type: 'string', description: 'Security protocol (TLS, SSL, or None)' }, + from: { type: 'string', description: 'Sender email address' }, + 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)' }, + fromName: { type: 'string', description: 'Display name for sender' }, + cc: { type: 'string', description: 'CC recipients (comma-separated)' }, + bcc: { type: 'string', description: 'BCC recipients (comma-separated)' }, + replyTo: { type: 'string', description: 'Reply-to email address' }, + attachments: { type: 'array', description: 'Files to attach (UserFile array)' }, + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the email was sent successfully' }, + messageId: { type: 'string', description: 'Message ID from SMTP server' }, + to: { type: 'string', description: 'Recipient email address' }, + subject: { type: 'string', description: 'Email subject' }, + error: { type: 'string', description: 'Error message if sending failed' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 2a97975ae5f..e7da0a7e577 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -41,8 +41,10 @@ import { JinaBlock } from '@/blocks/blocks/jina' import { JiraBlock } from '@/blocks/blocks/jira' import { KnowledgeBlock } from '@/blocks/blocks/knowledge' import { LinearBlock } from '@/blocks/blocks/linear' +import { LinkedInBlock } from '@/blocks/blocks/linkedin' import { LinkupBlock } from '@/blocks/blocks/linkup' import { MailchimpBlock } from '@/blocks/blocks/mailchimp' +import { MailgunBlock } from '@/blocks/blocks/mailgun' import { ManualTriggerBlock } from '@/blocks/blocks/manual_trigger' import { McpBlock } from '@/blocks/blocks/mcp' import { Mem0Block } from '@/blocks/blocks/mem0' @@ -79,6 +81,7 @@ import { SentryBlock } from '@/blocks/blocks/sentry' import { SerperBlock } from '@/blocks/blocks/serper' import { SharepointBlock } from '@/blocks/blocks/sharepoint' import { SlackBlock } from '@/blocks/blocks/slack' +import { SmtpBlock } from '@/blocks/blocks/smtp' import { StagehandBlock } from '@/blocks/blocks/stagehand' import { StagehandAgentBlock } from '@/blocks/blocks/stagehand_agent' import { StartTriggerBlock } from '@/blocks/blocks/start_trigger' @@ -153,6 +156,7 @@ export const registry: Record = { jira: JiraBlock, knowledge: KnowledgeBlock, linear: LinearBlock, + linkedin: LinkedInBlock, linkup: LinkupBlock, mailchimp: MailchimpBlock, mcp: McpBlock, @@ -180,6 +184,8 @@ export const registry: Record = { qdrant: QdrantBlock, resend: ResendBlock, sendgrid: SendGridBlock, + mailgun: MailgunBlock, + smtp: SmtpBlock, memory: MemoryBlock, reddit: RedditBlock, response: ResponseBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 38f990a2112..c848e36eb60 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -1464,11 +1464,17 @@ export function DiscordIcon(props: SVGProps) { export function LinkedInIcon(props: SVGProps) { return ( - - + + + + + ) } @@ -4386,3 +4392,39 @@ export function SendgridIcon(props: SVGProps) { ) } + +export function MailgunIcon(props: SVGProps) { + return ( + + + + ) +} + +export function SmtpIcon(props: SVGProps) { + return ( + + + + + ) +} diff --git a/apps/sim/lib/auth.ts b/apps/sim/lib/auth.ts index 48982d432a3..a22affc1290 100644 --- a/apps/sim/lib/auth.ts +++ b/apps/sim/lib/auth.ts @@ -157,6 +157,7 @@ export const auth = betterAuth({ 'asana', 'pipedrive', 'hubspot', + 'linkedin', // Common SSO provider patterns ...SSO_TRUSTED_PROVIDERS, @@ -1585,6 +1586,54 @@ export const auth = betterAuth({ } }, }, + // LinkedIn provider + { + providerId: 'linkedin', + clientId: env.LINKEDIN_CLIENT_ID as string, + clientSecret: env.LINKEDIN_CLIENT_SECRET as string, + authorizationUrl: 'https://www.linkedin.com/oauth/v2/authorization', + tokenUrl: 'https://www.linkedin.com/oauth/v2/accessToken', + userInfoUrl: 'https://api.linkedin.com/v2/userinfo', + scopes: ['profile', 'openid', 'email', 'w_member_social'], + responseType: 'code', + accessType: 'offline', + prompt: 'consent', + redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/linkedin`, + getUserInfo: async (tokens) => { + try { + logger.info('Fetching LinkedIn user profile') + + const response = await fetch('https://api.linkedin.com/v2/userinfo', { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + }) + + if (!response.ok) { + logger.error('Failed to fetch LinkedIn user info', { + status: response.status, + statusText: response.statusText, + }) + throw new Error('Failed to fetch user info') + } + + const profile = await response.json() + + return { + id: profile.sub, + name: profile.name || 'LinkedIn User', + email: profile.email || `${profile.sub}@linkedin.user`, + emailVerified: profile.email_verified || true, + image: profile.picture || undefined, + createdAt: new Date(), + updatedAt: new Date(), + } + } catch (error) { + logger.error('Error in LinkedIn getUserInfo:', { error }) + return null + } + }, + }, ], }), // Include SSO plugin when enabled diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index 660c5051ede..66cdb0ad47c 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -217,6 +217,8 @@ export const env = createEnv({ WEBFLOW_CLIENT_ID: z.string().optional(), // Webflow OAuth client ID WEBFLOW_CLIENT_SECRET: z.string().optional(), // Webflow OAuth client secret TRELLO_API_KEY: z.string().optional(), // Trello API Key + LINKEDIN_CLIENT_ID: z.string().optional(), // LinkedIn OAuth client ID + LINKEDIN_CLIENT_SECRET: z.string().optional(), // LinkedIn OAuth client secret // E2B Remote Code Execution E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 41145c3bf88..ed51f344339 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -15,6 +15,7 @@ import { HubspotIcon, JiraIcon, LinearIcon, + LinkedInIcon, MicrosoftExcelIcon, MicrosoftIcon, MicrosoftOneDriveIcon, @@ -59,6 +60,7 @@ export type OAuthProvider = | 'pipedrive' | 'hubspot' | 'salesforce' + | 'linkedin' | string export type OAuthService = @@ -94,6 +96,7 @@ export type OAuthService = | 'pipedrive' | 'hubspot' | 'salesforce' + | 'linkedin' export interface OAuthProviderConfig { id: OAuthProvider name: string @@ -732,6 +735,23 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'hubspot', }, + linkedin: { + id: 'linkedin', + name: 'LinkedIn', + icon: (props) => LinkedInIcon(props), + services: { + linkedin: { + id: 'linkedin', + name: 'LinkedIn', + description: 'Share posts and access profile data on LinkedIn.', + providerId: 'linkedin', + icon: (props) => LinkedInIcon(props), + baseProviderIcon: (props) => LinkedInIcon(props), + scopes: ['profile', 'openid', 'email', 'w_member_social'], + }, + }, + defaultService: 'linkedin', + }, salesforce: { id: 'salesforce', name: 'Salesforce', @@ -1219,6 +1239,19 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { supportsRefreshTokenRotation: true, } } + case 'linkedin': { + const { clientId, clientSecret } = getCredentials( + env.LINKEDIN_CLIENT_ID, + env.LINKEDIN_CLIENT_SECRET + ) + return { + tokenEndpoint: 'https://www.linkedin.com/oauth/v2/accessToken', + clientId, + clientSecret, + useBasicAuth: false, + supportsRefreshTokenRotation: false, + } + } case 'salesforce': { const { clientId, clientSecret } = getCredentials( env.SALESFORCE_CLIENT_ID, diff --git a/apps/sim/tools/linkedin/get_profile.ts b/apps/sim/tools/linkedin/get_profile.ts new file mode 100644 index 00000000000..90b23daf27c --- /dev/null +++ b/apps/sim/tools/linkedin/get_profile.ts @@ -0,0 +1,72 @@ +import type { ToolConfig } from '@/tools/types' + +interface GetProfileParams { + accessToken: string +} + +interface GetProfileResponse { + success: boolean + output: { + profile?: { + id: string + name: string + email?: string + picture?: string + } + } + error?: string +} + +export const linkedInGetProfileTool: ToolConfig = { + id: 'linkedin_get_profile', + name: 'Get LinkedIn Profile', + description: 'Retrieve your LinkedIn profile information', + version: '1.0.0', + + oauth: { + required: true, + provider: 'linkedin', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'Access token for LinkedIn API', + }, + }, + + request: { + url: () => 'https://api.linkedin.com/v2/userinfo', + method: 'GET', + headers: (params: GetProfileParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'X-Restli-Protocol-Version': '2.0.0', + }), + }, + + transformResponse: async (response: Response): Promise => { + if (!response.ok) { + return { + success: false, + output: {}, + error: `Failed to get profile: ${response.statusText}`, + } + } + + const profile = await response.json() + + return { + success: true, + output: { + profile: { + id: profile.sub, + name: profile.name, + email: profile.email, + picture: profile.picture, + }, + }, + } + }, +} diff --git a/apps/sim/tools/linkedin/index.ts b/apps/sim/tools/linkedin/index.ts new file mode 100644 index 00000000000..7b457c6cab1 --- /dev/null +++ b/apps/sim/tools/linkedin/index.ts @@ -0,0 +1,2 @@ +export { linkedInGetProfileTool } from './get_profile' +export { linkedInSharePostTool } from './share_post' diff --git a/apps/sim/tools/linkedin/share_post.ts b/apps/sim/tools/linkedin/share_post.ts new file mode 100644 index 00000000000..1ef99dbe165 --- /dev/null +++ b/apps/sim/tools/linkedin/share_post.ts @@ -0,0 +1,163 @@ +import type { ToolConfig } from '@/tools/types' + +interface SharePostParams { + text: string + visibility: 'PUBLIC' | 'CONNECTIONS' + accessToken: string +} + +interface SharePostResponse { + success: boolean + output: { + postId?: string + } + error?: string +} + +export const linkedInSharePostTool: ToolConfig = { + id: 'linkedin_share_post', + name: 'Share Post on LinkedIn', + description: 'Share a post to your personal LinkedIn feed', + version: '1.0.0', + + oauth: { + required: true, + provider: 'linkedin', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'Access token for LinkedIn API', + }, + text: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The text content of your LinkedIn post', + }, + visibility: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Who can see this post: "PUBLIC" or "CONNECTIONS" (default: "PUBLIC")', + }, + }, + + // First request: Get user profile to obtain the person URN + request: { + url: () => 'https://api.linkedin.com/v2/userinfo', + method: 'GET', + headers: (params: SharePostParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'X-Restli-Protocol-Version': '2.0.0', + }), + }, + + // Use postProcess to make the actual post creation request + postProcess: async (profileResult, params, executeTool) => { + try { + // Extract profile from the first request + if (!profileResult.success || !profileResult.output) { + return { + success: false, + output: {}, + error: 'Failed to fetch user profile', + } + } + + // Get profile data from output + const profile = profileResult.output as any + + // Determine the person URN - handle both direct profile response and wrapped response + let sub: string + if (profile.profile?.id) { + sub = profile.profile.id + } else if (profile.sub) { + sub = profile.sub + } else if (profile.id) { + sub = profile.id + } else { + return { + success: false, + output: {}, + error: 'Could not determine user ID from profile', + } + } + + const authorUrn = `urn:li:person:${sub}` + + // Create the post + const postData = { + author: authorUrn, + lifecycleState: 'PUBLISHED', + specificContent: { + 'com.linkedin.ugc.ShareContent': { + shareCommentary: { + text: params.text, + }, + shareMediaCategory: 'NONE', + }, + }, + visibility: { + 'com.linkedin.ugc.MemberNetworkVisibility': params.visibility || 'PUBLIC', + }, + } + + const response = await fetch('https://api.linkedin.com/v2/ugcPosts', { + method: 'POST', + headers: { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + 'X-Restli-Protocol-Version': '2.0.0', + }, + body: JSON.stringify(postData), + }) + + if (!response.ok) { + const error = await response.text() + return { + success: false, + output: {}, + error: `LinkedIn API error: ${error}`, + } + } + + const result = await response.json() + + return { + success: true, + output: { + postId: result.id, + }, + } + } catch (error) { + return { + success: false, + output: {}, + error: error instanceof Error ? error.message : 'Unknown error', + } + } + }, + + transformResponse: async (response: Response): Promise => { + // This handles the initial profile fetch response + if (!response.ok) { + return { + success: false, + output: {}, + error: `Failed to fetch profile: ${response.statusText}`, + } + } + + const profile = await response.json() + + // Return profile data for postProcess to use + return { + success: true, + output: profile, + } + }, +} diff --git a/apps/sim/tools/linkedin/types.ts b/apps/sim/tools/linkedin/types.ts new file mode 100644 index 00000000000..eca93c851b1 --- /dev/null +++ b/apps/sim/tools/linkedin/types.ts @@ -0,0 +1,49 @@ +export interface LinkedInProfile { + sub: string + name: string + given_name: string + family_name: string + email?: string + picture?: string + email_verified?: boolean +} + +export interface LinkedInPost { + author: string // URN format: urn:li:person:abc123 + lifecycleState: 'PUBLISHED' + specificContent: { + 'com.linkedin.ugc.ShareContent': { + shareCommentary: { + text: string + } + shareMediaCategory: 'NONE' | 'ARTICLE' | 'IMAGE' + media?: Array<{ + status: 'READY' + description: { + text: string + } + media: string // URN format + title: { + text: string + } + }> + } + } + visibility: { + 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' | 'CONNECTIONS' + } +} + +export type LinkedInResponse = { + success: boolean + output: { + postId?: string + profile?: { + id: string + name: string + email?: string + picture?: string + } + } + error?: string +} diff --git a/apps/sim/tools/mailgun/add_list_member.ts b/apps/sim/tools/mailgun/add_list_member.ts new file mode 100644 index 00000000000..6e33e5a95bf --- /dev/null +++ b/apps/sim/tools/mailgun/add_list_member.ts @@ -0,0 +1,101 @@ +import type { AddListMemberParams, AddListMemberResult } from '@/tools/mailgun/types' +import type { ToolConfig } from '@/tools/types' + +export const mailgunAddListMemberTool: ToolConfig = { + id: 'mailgun_add_list_member', + name: 'Mailgun Add List Member', + description: 'Add a member to a mailing list', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun API key', + }, + listAddress: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Mailing list address', + }, + address: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Member email address', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Member name', + }, + vars: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON string of custom variables', + }, + subscribed: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the member is subscribed', + }, + }, + + request: { + url: (params) => `https://api.mailgun.net/v3/lists/${params.listAddress}/members`, + method: 'POST', + headers: (params) => ({ + Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`, + }), + body: (params) => { + const formData = new FormData() + formData.append('address', params.address) + + if (params.name) { + formData.append('name', params.name) + } + if (params.vars) { + formData.append('vars', params.vars) + } + if (params.subscribed !== undefined) { + formData.append('subscribed', params.subscribed ? 'yes' : 'no') + } + + return { body: formData } + }, + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to add list member') + } + + const result = await response.json() + + return { + success: true, + output: { + success: true, + message: result.message, + member: { + address: result.member.address, + name: result.member.name, + subscribed: result.member.subscribed, + vars: result.member.vars, + }, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the member was added successfully' }, + message: { type: 'string', description: 'Response message' }, + member: { type: 'json', description: 'Added member details' }, + }, +} diff --git a/apps/sim/tools/mailgun/create_mailing_list.ts b/apps/sim/tools/mailgun/create_mailing_list.ts new file mode 100644 index 00000000000..6d7f5d66291 --- /dev/null +++ b/apps/sim/tools/mailgun/create_mailing_list.ts @@ -0,0 +1,99 @@ +import type { CreateMailingListParams, CreateMailingListResult } from '@/tools/mailgun/types' +import type { ToolConfig } from '@/tools/types' + +export const mailgunCreateMailingListTool: ToolConfig< + CreateMailingListParams, + CreateMailingListResult +> = { + id: 'mailgun_create_mailing_list', + name: 'Mailgun Create Mailing List', + description: 'Create a new mailing list', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun API key', + }, + address: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Mailing list address (e.g., list@example.com)', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Mailing list name', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Mailing list description', + }, + accessLevel: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Access level: readonly, members, or everyone', + }, + }, + + request: { + url: () => 'https://api.mailgun.net/v3/lists', + method: 'POST', + headers: (params) => ({ + Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`, + }), + body: (params) => { + const formData = new FormData() + formData.append('address', params.address) + + if (params.name) { + formData.append('name', params.name) + } + if (params.description) { + formData.append('description', params.description) + } + if (params.accessLevel) { + formData.append('access_level', params.accessLevel) + } + + return { body: formData } + }, + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to create mailing list') + } + + const result = await response.json() + + return { + success: true, + output: { + success: true, + message: result.message, + list: { + address: result.list.address, + name: result.list.name, + description: result.list.description, + accessLevel: result.list.access_level, + createdAt: result.list.created_at, + }, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the list was created successfully' }, + message: { type: 'string', description: 'Response message' }, + list: { type: 'json', description: 'Created mailing list details' }, + }, +} diff --git a/apps/sim/tools/mailgun/get_domain.ts b/apps/sim/tools/mailgun/get_domain.ts new file mode 100644 index 00000000000..7b8ce4104a3 --- /dev/null +++ b/apps/sim/tools/mailgun/get_domain.ts @@ -0,0 +1,62 @@ +import type { GetDomainParams, GetDomainResult } from '@/tools/mailgun/types' +import type { ToolConfig } from '@/tools/types' + +export const mailgunGetDomainTool: ToolConfig = { + id: 'mailgun_get_domain', + name: 'Mailgun Get Domain', + description: 'Get details of a specific domain', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun API key', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Domain name', + }, + }, + + request: { + url: (params) => `https://api.mailgun.net/v3/domains/${params.domain}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`, + }), + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to get domain') + } + + const result = await response.json() + + return { + success: true, + output: { + success: true, + domain: { + name: result.domain.name, + smtpLogin: result.domain.smtp_login, + smtpPassword: result.domain.smtp_password, + spamAction: result.domain.spam_action, + state: result.domain.state, + createdAt: result.domain.created_at, + type: result.domain.type, + }, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the request was successful' }, + domain: { type: 'json', description: 'Domain details' }, + }, +} diff --git a/apps/sim/tools/mailgun/get_mailing_list.ts b/apps/sim/tools/mailgun/get_mailing_list.ts new file mode 100644 index 00000000000..509021d8a5f --- /dev/null +++ b/apps/sim/tools/mailgun/get_mailing_list.ts @@ -0,0 +1,61 @@ +import type { GetMailingListParams, GetMailingListResult } from '@/tools/mailgun/types' +import type { ToolConfig } from '@/tools/types' + +export const mailgunGetMailingListTool: ToolConfig = { + id: 'mailgun_get_mailing_list', + name: 'Mailgun Get Mailing List', + description: 'Get details of a mailing list', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun API key', + }, + address: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Mailing list address', + }, + }, + + request: { + url: (params) => `https://api.mailgun.net/v3/lists/${params.address}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`, + }), + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to get mailing list') + } + + const result = await response.json() + + return { + success: true, + output: { + success: true, + list: { + address: result.list.address, + name: result.list.name, + description: result.list.description, + accessLevel: result.list.access_level, + membersCount: result.list.members_count, + createdAt: result.list.created_at, + }, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the request was successful' }, + list: { type: 'json', description: 'Mailing list details' }, + }, +} diff --git a/apps/sim/tools/mailgun/get_message.ts b/apps/sim/tools/mailgun/get_message.ts new file mode 100644 index 00000000000..22066e58730 --- /dev/null +++ b/apps/sim/tools/mailgun/get_message.ts @@ -0,0 +1,83 @@ +import type { GetMessageParams, GetMessageResult } from '@/tools/mailgun/types' +import type { ToolConfig } from '@/tools/types' + +export const mailgunGetMessageTool: ToolConfig = { + id: 'mailgun_get_message', + name: 'Mailgun Get Message', + description: 'Retrieve a stored message by its key', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun API key', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun domain', + }, + messageKey: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Message storage key', + }, + }, + + request: { + url: (params) => + `https://api.mailgun.net/v3/domains/${params.domain}/messages/${params.messageKey}`, + method: 'GET', + headers: (params) => ({ + Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`, + }), + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to get message') + } + + const result = await response.json() + + return { + success: true, + output: { + success: true, + recipients: result.recipients, + from: result.from, + subject: result.subject, + bodyPlain: result['body-plain'], + strippedText: result['stripped-text'], + strippedSignature: result['stripped-signature'], + bodyHtml: result['body-html'], + strippedHtml: result['stripped-html'], + attachmentCount: result['attachment-count'], + timestamp: result.timestamp, + messageHeaders: result['message-headers'], + contentIdMap: result['content-id-map'], + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the request was successful' }, + recipients: { type: 'string', description: 'Message recipients' }, + from: { type: 'string', description: 'Sender email' }, + subject: { type: 'string', description: 'Message subject' }, + bodyPlain: { type: 'string', description: 'Plain text body' }, + strippedText: { type: 'string', description: 'Stripped text' }, + strippedSignature: { type: 'string', description: 'Stripped signature' }, + bodyHtml: { type: 'string', description: 'HTML body' }, + strippedHtml: { type: 'string', description: 'Stripped HTML' }, + attachmentCount: { type: 'number', description: 'Number of attachments' }, + timestamp: { type: 'number', description: 'Message timestamp' }, + messageHeaders: { type: 'json', description: 'Message headers' }, + contentIdMap: { type: 'json', description: 'Content ID map' }, + }, +} diff --git a/apps/sim/tools/mailgun/index.ts b/apps/sim/tools/mailgun/index.ts new file mode 100644 index 00000000000..2e9bf290357 --- /dev/null +++ b/apps/sim/tools/mailgun/index.ts @@ -0,0 +1,11 @@ +// Message Operations + +export { mailgunAddListMemberTool } from './add_list_member' +export { mailgunCreateMailingListTool } from './create_mailing_list' +export { mailgunGetDomainTool } from './get_domain' +export { mailgunGetMailingListTool } from './get_mailing_list' +export { mailgunGetMessageTool } from './get_message' +export { mailgunListDomainsTool } from './list_domains' +export { mailgunListMessagesTool } from './list_messages' +export { mailgunSendMessageTool } from './send_message' +export type { SendMessageParams, SendMessageResult } from './types' diff --git a/apps/sim/tools/mailgun/list_domains.ts b/apps/sim/tools/mailgun/list_domains.ts new file mode 100644 index 00000000000..4735d30aa54 --- /dev/null +++ b/apps/sim/tools/mailgun/list_domains.ts @@ -0,0 +1,50 @@ +import type { ListDomainsParams, ListDomainsResult } from '@/tools/mailgun/types' +import type { ToolConfig } from '@/tools/types' + +export const mailgunListDomainsTool: ToolConfig = { + id: 'mailgun_list_domains', + name: 'Mailgun List Domains', + description: 'List all domains for your Mailgun account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun API key', + }, + }, + + request: { + url: () => 'https://api.mailgun.net/v3/domains', + method: 'GET', + headers: (params) => ({ + Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`, + }), + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to list domains') + } + + const result = await response.json() + + return { + success: true, + output: { + success: true, + totalCount: result.total_count, + items: result.items || [], + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the request was successful' }, + totalCount: { type: 'number', description: 'Total number of domains' }, + items: { type: 'json', description: 'Array of domain objects' }, + }, +} diff --git a/apps/sim/tools/mailgun/list_messages.ts b/apps/sim/tools/mailgun/list_messages.ts new file mode 100644 index 00000000000..59a269d39d5 --- /dev/null +++ b/apps/sim/tools/mailgun/list_messages.ts @@ -0,0 +1,81 @@ +import type { ListMessagesParams, ListMessagesResult } from '@/tools/mailgun/types' +import type { ToolConfig } from '@/tools/types' + +export const mailgunListMessagesTool: ToolConfig = { + id: 'mailgun_list_messages', + name: 'Mailgun List Messages', + description: 'List events (logs) for messages sent through Mailgun', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun API key', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun domain', + }, + event: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by event type (accepted, delivered, failed, opened, clicked, etc.)', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of events to return (default: 100)', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.mailgun.net/v3/${params.domain}/events` + const queryParams = new URLSearchParams() + + if (params.event) { + queryParams.append('event', params.event) + } + if (params.limit) { + queryParams.append('limit', params.limit.toString()) + } + + const query = queryParams.toString() + return query ? `${baseUrl}?${query}` : baseUrl + }, + method: 'GET', + headers: (params) => ({ + Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`, + }), + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to list messages') + } + + const result = await response.json() + + return { + success: true, + output: { + success: true, + items: result.items || [], + paging: result.paging || {}, + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the request was successful' }, + items: { type: 'json', description: 'Array of event items' }, + paging: { type: 'json', description: 'Paging information' }, + }, +} diff --git a/apps/sim/tools/mailgun/send_message.ts b/apps/sim/tools/mailgun/send_message.ts new file mode 100644 index 00000000000..c4e7a9735c2 --- /dev/null +++ b/apps/sim/tools/mailgun/send_message.ts @@ -0,0 +1,129 @@ +import type { SendMessageParams, SendMessageResult } from '@/tools/mailgun/types' +import type { ToolConfig } from '@/tools/types' + +export const mailgunSendMessageTool: ToolConfig = { + id: 'mailgun_send_message', + name: 'Mailgun Send Message', + description: 'Send an email using Mailgun API', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun API key', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Mailgun domain (e.g., mg.example.com)', + }, + from: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Sender email address', + }, + to: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Recipient email address (comma-separated for multiple)', + }, + subject: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email subject', + }, + text: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Plain text body of the email', + }, + html: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'HTML body of the email', + }, + cc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'CC email address (comma-separated for multiple)', + }, + bcc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'BCC email address (comma-separated for multiple)', + }, + tags: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Tags for the email (comma-separated)', + }, + }, + + request: { + url: (params) => `https://api.mailgun.net/v3/${params.domain}/messages`, + method: 'POST', + headers: (params) => ({ + Authorization: `Basic ${Buffer.from(`api:${params.apiKey}`).toString('base64')}`, + }), + body: (params) => { + const formData = new FormData() + formData.append('from', params.from) + formData.append('to', params.to) + formData.append('subject', params.subject) + + if (params.text) { + formData.append('text', params.text) + } + if (params.html) { + formData.append('html', params.html) + } + if (params.cc) { + formData.append('cc', params.cc) + } + if (params.bcc) { + formData.append('bcc', params.bcc) + } + if (params.tags) { + const tagArray = params.tags.split(',').map((t) => t.trim()) + tagArray.forEach((tag) => formData.append('o:tag', tag)) + } + + return { body: formData } + }, + }, + + transformResponse: async (response, params): Promise => { + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || 'Failed to send message') + } + + const result = await response.json() + + return { + success: true, + output: { + success: true, + id: result.id, + message: result.message || 'Queued. Thank you.', + }, + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the message was sent successfully' }, + id: { type: 'string', description: 'Message ID' }, + message: { type: 'string', description: 'Response message from Mailgun' }, + }, +} diff --git a/apps/sim/tools/mailgun/types.ts b/apps/sim/tools/mailgun/types.ts new file mode 100644 index 00000000000..dcd495510db --- /dev/null +++ b/apps/sim/tools/mailgun/types.ts @@ -0,0 +1,164 @@ +import type { ToolResponse } from '@/tools/types' + +// Send Message +export interface SendMessageParams { + apiKey: string + domain: string + from: string + to: string + subject: string + text?: string + html?: string + cc?: string + bcc?: string + tags?: string +} + +export interface SendMessageResult extends ToolResponse { + output: { + success: boolean + id: string + message: string + } +} + +// Get Message +export interface GetMessageParams { + apiKey: string + domain: string + messageKey: string +} + +export interface GetMessageResult extends ToolResponse { + output: { + success: boolean + recipients: string + from: string + subject: string + bodyPlain: string + strippedText: string + strippedSignature: string + bodyHtml: string + strippedHtml: string + attachmentCount: number + timestamp: number + messageHeaders: any + contentIdMap: any + } +} + +// List Messages (Events) +export interface ListMessagesParams { + apiKey: string + domain: string + event?: string + limit?: number +} + +export interface ListMessagesResult extends ToolResponse { + output: { + success: boolean + items: any[] + paging: any + } +} + +// Create Mailing List +export interface CreateMailingListParams { + apiKey: string + address: string + name?: string + description?: string + accessLevel?: 'readonly' | 'members' | 'everyone' +} + +export interface CreateMailingListResult extends ToolResponse { + output: { + success: boolean + message: string + list: { + address: string + name: string + description: string + accessLevel: string + createdAt: string + } + } +} + +// Get Mailing List +export interface GetMailingListParams { + apiKey: string + address: string +} + +export interface GetMailingListResult extends ToolResponse { + output: { + success: boolean + list: { + address: string + name: string + description: string + accessLevel: string + membersCount: number + createdAt: string + } + } +} + +// Add List Member +export interface AddListMemberParams { + apiKey: string + listAddress: string + address: string + name?: string + vars?: string + subscribed?: boolean +} + +export interface AddListMemberResult extends ToolResponse { + output: { + success: boolean + message: string + member: { + address: string + name: string + subscribed: boolean + vars: any + } + } +} + +// List Domains +export interface ListDomainsParams { + apiKey: string +} + +export interface ListDomainsResult extends ToolResponse { + output: { + success: boolean + totalCount: number + items: any[] + } +} + +// Get Domain +export interface GetDomainParams { + apiKey: string + domain: string +} + +export interface GetDomainResult extends ToolResponse { + output: { + success: boolean + domain: { + name: string + smtpLogin: string + smtpPassword: string + spamAction: string + state: string + createdAt: string + type: string + } + } +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index be2ae914404..9d993891b65 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -419,6 +419,7 @@ import { linearUpdateProjectTool, linearUpdateWorkflowStateTool, } from '@/tools/linear' +import { linkedInGetProfileTool, linkedInSharePostTool } from '@/tools/linkedin' import { linkupSearchTool } from '@/tools/linkup' import { mailchimpAddMemberTagsTool, @@ -495,6 +496,16 @@ import { mailchimpUpdateSegmentTool, mailchimpUpdateTemplateTool, } from '@/tools/mailchimp' +import { + mailgunAddListMemberTool, + mailgunCreateMailingListTool, + mailgunGetDomainTool, + mailgunGetMailingListTool, + mailgunGetMessageTool, + mailgunListDomainsTool, + mailgunListMessagesTool, + mailgunSendMessageTool, +} from '@/tools/mailgun' import { mem0AddMemoriesTool, mem0GetMemoriesTool, mem0SearchMemoriesTool } from '@/tools/mem0' import { memoryAddTool, memoryDeleteTool, memoryGetAllTool, memoryGetTool } from '@/tools/memory' import { @@ -809,6 +820,7 @@ import { slackUpdateMessageTool, } from '@/tools/slack' import { smsSendTool } from '@/tools/sms' +import { smtpSendMailTool } from '@/tools/smtp' import { stagehandAgentTool, stagehandExtractTool } from '@/tools/stagehand' import { stripeCancelPaymentIntentTool, @@ -1043,6 +1055,8 @@ export const tools: Record = { jina_read_url: readUrlTool, jina_search: jinaSearchTool, linkup_search: linkupSearchTool, + linkedin_share_post: linkedInSharePostTool, + linkedin_get_profile: linkedInGetProfileTool, resend_send: mailSendTool, sendgrid_send_mail: sendGridSendMailTool, sendgrid_add_contact: sendGridAddContactTool, @@ -1060,6 +1074,15 @@ export const tools: Record = { sendgrid_list_templates: sendGridListTemplatesTool, sendgrid_delete_template: sendGridDeleteTemplateTool, sendgrid_create_template_version: sendGridCreateTemplateVersionTool, + smtp_send_mail: smtpSendMailTool, + mailgun_send_message: mailgunSendMessageTool, + mailgun_get_message: mailgunGetMessageTool, + mailgun_list_messages: mailgunListMessagesTool, + mailgun_create_mailing_list: mailgunCreateMailingListTool, + mailgun_get_mailing_list: mailgunGetMailingListTool, + mailgun_add_list_member: mailgunAddListMemberTool, + mailgun_list_domains: mailgunListDomainsTool, + mailgun_get_domain: mailgunGetDomainTool, sms_send: smsSendTool, jira_retrieve: jiraRetrieveTool, jira_update: jiraUpdateTool, diff --git a/apps/sim/tools/sendgrid/add_contact.ts b/apps/sim/tools/sendgrid/add_contact.ts index c89d2a7814e..9bde449ce17 100644 --- a/apps/sim/tools/sendgrid/add_contact.ts +++ b/apps/sim/tools/sendgrid/add_contact.ts @@ -36,7 +36,8 @@ export const sendGridAddContactTool: ToolConfig type: 'json', required: false, visibility: 'user-or-llm', - description: 'JSON object of custom field key-value pairs', + description: + 'JSON object of custom field key-value pairs (use field IDs like e1_T, e2_N, e3_D, not field names)', }, listIds: { type: 'string', diff --git a/apps/sim/tools/sendgrid/send_mail.ts b/apps/sim/tools/sendgrid/send_mail.ts index 7b44a4e6873..51119a38aac 100644 --- a/apps/sim/tools/sendgrid/send_mail.ts +++ b/apps/sim/tools/sendgrid/send_mail.ts @@ -81,11 +81,10 @@ export const sendGridSendMailTool: ToolConfig = description: 'Reply-to name', }, attachments: { - type: 'json', + type: 'file[]', required: false, - visibility: 'user-or-llm', - description: - 'JSON array of attachment objects with content (base64), filename, type, and disposition', + visibility: 'user-only', + description: 'Files to attach to the email', }, templateId: { type: 'string', diff --git a/apps/sim/tools/sendgrid/types.ts b/apps/sim/tools/sendgrid/types.ts index 4e187fc4bd2..4fd395adc45 100644 --- a/apps/sim/tools/sendgrid/types.ts +++ b/apps/sim/tools/sendgrid/types.ts @@ -5,22 +5,21 @@ export interface SendGridBaseParams { apiKey: string } -// Mail Send types export interface SendMailParams extends SendGridBaseParams { from: string fromName?: string to: string toName?: string - subject: string - content: string + subject?: string + content?: string contentType?: 'text/plain' | 'text/html' cc?: string bcc?: string replyTo?: string replyToName?: string - attachments?: string // JSON string of attachment objects + attachments?: string templateId?: string - dynamicTemplateData?: string // JSON string + dynamicTemplateData?: string } export interface SendMailResult extends ToolResponse { diff --git a/apps/sim/tools/smtp/index.ts b/apps/sim/tools/smtp/index.ts new file mode 100644 index 00000000000..795e624955e --- /dev/null +++ b/apps/sim/tools/smtp/index.ts @@ -0,0 +1,2 @@ +export { smtpSendMailTool } from './send_mail' +export type { SmtpConnectionConfig, SmtpSendMailParams, SmtpSendMailResult } from './types' diff --git a/apps/sim/tools/smtp/send_mail.ts b/apps/sim/tools/smtp/send_mail.ts new file mode 100644 index 00000000000..bc189d62d52 --- /dev/null +++ b/apps/sim/tools/smtp/send_mail.ts @@ -0,0 +1,177 @@ +import type { SmtpSendMailParams, SmtpSendMailResult } from '@/tools/smtp/types' +import type { ToolConfig } from '@/tools/types' + +export const smtpSendMailTool: ToolConfig = { + id: 'smtp_send_mail', + name: 'SMTP Send Mail', + description: 'Send emails via SMTP server', + version: '1.0.0', + + params: { + smtpHost: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SMTP server hostname (e.g., smtp.gmail.com)', + }, + smtpPort: { + type: 'number', + required: true, + visibility: 'user-only', + description: 'SMTP server port (587 for TLS, 465 for SSL)', + }, + smtpUsername: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SMTP authentication username', + }, + smtpPassword: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'SMTP authentication password', + }, + smtpSecure: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Security protocol (TLS, SSL, or None)', + }, + + from: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Sender email address', + }, + to: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Recipient email address', + }, + subject: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email subject', + }, + body: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email body content', + }, + contentType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Content type (text or html)', + }, + + // Optional Fields + fromName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Display name for sender', + }, + cc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'CC recipients (comma-separated)', + }, + bcc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'BCC recipients (comma-separated)', + }, + replyTo: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Reply-to email address', + }, + attachments: { + type: 'file[]', + required: false, + visibility: 'user-only', + description: 'Files to attach to the email', + }, + }, + + request: { + url: '/api/tools/smtp/send', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params: SmtpSendMailParams) => ({ + smtpHost: params.smtpHost, + smtpPort: params.smtpPort, + smtpUsername: params.smtpUsername, + smtpPassword: params.smtpPassword, + smtpSecure: params.smtpSecure, + from: params.from, + to: params.to, + subject: params.subject, + body: params.body, + contentType: params.contentType || 'text', + fromName: params.fromName, + cc: params.cc, + bcc: params.bcc, + replyTo: params.replyTo, + attachments: params.attachments, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + output: { + success: false, + }, + error: data.error || 'Failed to send email via SMTP', + } + } + + return { + success: true, + output: { + success: true, + messageId: data.messageId, + to: data.to, + subject: data.subject, + }, + } + }, + + outputs: { + success: { + type: 'boolean', + description: 'Whether the email was sent successfully', + }, + messageId: { + type: 'string', + description: 'Message ID from SMTP server', + }, + to: { + type: 'string', + description: 'Recipient email address', + }, + subject: { + type: 'string', + description: 'Email subject', + }, + error: { + type: 'string', + description: 'Error message if sending failed', + }, + }, +} diff --git a/apps/sim/tools/smtp/types.ts b/apps/sim/tools/smtp/types.ts new file mode 100644 index 00000000000..9792262a960 --- /dev/null +++ b/apps/sim/tools/smtp/types.ts @@ -0,0 +1,34 @@ +import type { ToolResponse } from '@/tools/types' + +export interface SmtpConnectionConfig { + smtpHost: string + smtpPort: number + smtpUsername: string + smtpPassword: string + smtpSecure: 'TLS' | 'SSL' | 'None' +} + +export interface SmtpSendMailParams extends SmtpConnectionConfig { + // Email content + from: string + to: string + subject: string + body: string + contentType?: 'text' | 'html' + + // Optional fields + fromName?: string + cc?: string + bcc?: string + replyTo?: string + attachments?: any[] +} + +export interface SmtpSendMailResult extends ToolResponse { + output: { + success: boolean + messageId?: string + to?: string + subject?: string + } +} diff --git a/bun.lock b/bun.lock index 7beee8de412..231c8e38ad2 100644 --- a/bun.lock +++ b/bun.lock @@ -16,6 +16,7 @@ "fluent-ffmpeg": "2.1.3", "mongodb": "6.19.0", "neo4j-driver": "6.0.1", + "nodemailer": "7.0.11", "onedollarstats": "0.0.10", "postgres": "^3.4.5", "remark-gfm": "4.0.1", @@ -27,6 +28,7 @@ "@next/env": "15.4.1", "@octokit/rest": "^21.0.0", "@tailwindcss/typography": "0.5.19", + "@types/nodemailer": "7.0.4", "drizzle-kit": "^0.31.4", "husky": "9.1.7", "lint-staged": "16.0.0", @@ -313,6 +315,8 @@ "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.899.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.899.0", "@aws-sdk/credential-provider-node": "3.899.0", "@aws-sdk/middleware-bucket-endpoint": "3.893.0", "@aws-sdk/middleware-expect-continue": "3.893.0", "@aws-sdk/middleware-flexible-checksums": "3.899.0", "@aws-sdk/middleware-host-header": "3.893.0", "@aws-sdk/middleware-location-constraint": "3.893.0", "@aws-sdk/middleware-logger": "3.893.0", "@aws-sdk/middleware-recursion-detection": "3.893.0", "@aws-sdk/middleware-sdk-s3": "3.899.0", "@aws-sdk/middleware-ssec": "3.893.0", "@aws-sdk/middleware-user-agent": "3.899.0", "@aws-sdk/region-config-resolver": "3.893.0", "@aws-sdk/signature-v4-multi-region": "3.899.0", "@aws-sdk/types": "3.893.0", "@aws-sdk/util-endpoints": "3.895.0", "@aws-sdk/util-user-agent-browser": "3.893.0", "@aws-sdk/util-user-agent-node": "3.899.0", "@aws-sdk/xml-builder": "3.894.0", "@smithy/config-resolver": "^4.2.2", "@smithy/core": "^3.13.0", "@smithy/eventstream-serde-browser": "^4.1.1", "@smithy/eventstream-serde-config-resolver": "^4.2.1", "@smithy/eventstream-serde-node": "^4.1.1", "@smithy/fetch-http-handler": "^5.2.1", "@smithy/hash-blob-browser": "^4.1.1", "@smithy/hash-node": "^4.1.1", "@smithy/hash-stream-node": "^4.1.1", "@smithy/invalid-dependency": "^4.1.1", "@smithy/md5-js": "^4.1.1", "@smithy/middleware-content-length": "^4.1.1", "@smithy/middleware-endpoint": "^4.2.5", "@smithy/middleware-retry": "^4.3.1", "@smithy/middleware-serde": "^4.1.1", "@smithy/middleware-stack": "^4.1.1", "@smithy/node-config-provider": "^4.2.2", "@smithy/node-http-handler": "^4.2.1", "@smithy/protocol-http": "^5.2.1", "@smithy/smithy-client": "^4.6.5", "@smithy/types": "^4.5.0", "@smithy/url-parser": "^4.1.1", "@smithy/util-base64": "^4.1.0", "@smithy/util-body-length-browser": "^4.1.0", "@smithy/util-body-length-node": "^4.1.0", "@smithy/util-defaults-mode-browser": "^4.1.5", "@smithy/util-defaults-mode-node": "^4.1.5", "@smithy/util-endpoints": "^3.1.2", "@smithy/util-middleware": "^4.1.1", "@smithy/util-retry": "^4.1.2", "@smithy/util-stream": "^4.3.2", "@smithy/util-utf8": "^4.1.0", "@smithy/util-waiter": "^4.1.1", "@smithy/uuid": "^1.0.0", "tslib": "^2.6.2" } }, "sha512-m/XQT0Rew4ff1Xmug+8n7f3uwom2DhbPwKWjUpluKo8JNCJJTIlfFSe1tnSimeE7RdLcIigK0YpvE50OjZZHGw=="], + "@aws-sdk/client-sesv2": ["@aws-sdk/client-sesv2@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-node": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/signature-v4-multi-region": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jDQ4x2HwB2/UXBS7CTeSDiIb+sVsYGDyxTeXdrRAtqNdGv8kC54fbwokDiJ/mnMyB2gyXWw57BqeDJNkZuLmsw=="], + "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.899.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.899.0", "@aws-sdk/middleware-host-header": "3.893.0", "@aws-sdk/middleware-logger": "3.893.0", "@aws-sdk/middleware-recursion-detection": "3.893.0", "@aws-sdk/middleware-user-agent": "3.899.0", "@aws-sdk/region-config-resolver": "3.893.0", "@aws-sdk/types": "3.893.0", "@aws-sdk/util-endpoints": "3.895.0", "@aws-sdk/util-user-agent-browser": "3.893.0", "@aws-sdk/util-user-agent-node": "3.899.0", "@smithy/config-resolver": "^4.2.2", "@smithy/core": "^3.13.0", "@smithy/fetch-http-handler": "^5.2.1", "@smithy/hash-node": "^4.1.1", "@smithy/invalid-dependency": "^4.1.1", "@smithy/middleware-content-length": "^4.1.1", "@smithy/middleware-endpoint": "^4.2.5", "@smithy/middleware-retry": "^4.3.1", "@smithy/middleware-serde": "^4.1.1", "@smithy/middleware-stack": "^4.1.1", "@smithy/node-config-provider": "^4.2.2", "@smithy/node-http-handler": "^4.2.1", "@smithy/protocol-http": "^5.2.1", "@smithy/smithy-client": "^4.6.5", "@smithy/types": "^4.5.0", "@smithy/url-parser": "^4.1.1", "@smithy/util-base64": "^4.1.0", "@smithy/util-body-length-browser": "^4.1.0", "@smithy/util-body-length-node": "^4.1.0", "@smithy/util-defaults-mode-browser": "^4.1.5", "@smithy/util-defaults-mode-node": "^4.1.5", "@smithy/util-endpoints": "^3.1.2", "@smithy/util-middleware": "^4.1.1", "@smithy/util-retry": "^4.1.2", "@smithy/util-utf8": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-EKz/iiVDv2OC8/3ONcXG3+rhphx9Heh7KXQdsZzsAXGVn6mWtrHQLrWjgONckmK4LrD07y4+5WlJlGkMxSMA5A=="], "@aws-sdk/core": ["@aws-sdk/core@3.899.0", "", { "dependencies": { "@aws-sdk/types": "3.893.0", "@aws-sdk/xml-builder": "3.894.0", "@smithy/core": "^3.13.0", "@smithy/node-config-provider": "^4.2.2", "@smithy/property-provider": "^4.1.1", "@smithy/protocol-http": "^5.2.1", "@smithy/signature-v4": "^5.2.1", "@smithy/smithy-client": "^4.6.5", "@smithy/types": "^4.5.0", "@smithy/util-base64": "^4.1.0", "@smithy/util-middleware": "^4.1.1", "@smithy/util-utf8": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-Enp5Zw37xaRlnscyaelaUZNxVqyE3CTS8gjahFbW2bbzVtRD2itHBVgq8A3lvKiFb7Feoxa71aTe0fQ1I6AhQQ=="], @@ -323,6 +327,8 @@ "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.899.0", "", { "dependencies": { "@aws-sdk/core": "3.899.0", "@aws-sdk/credential-provider-env": "3.899.0", "@aws-sdk/credential-provider-http": "3.899.0", "@aws-sdk/credential-provider-process": "3.899.0", "@aws-sdk/credential-provider-sso": "3.899.0", "@aws-sdk/credential-provider-web-identity": "3.899.0", "@aws-sdk/nested-clients": "3.899.0", "@aws-sdk/types": "3.893.0", "@smithy/credential-provider-imds": "^4.1.2", "@smithy/property-provider": "^4.1.1", "@smithy/shared-ini-file-loader": "^4.2.0", "@smithy/types": "^4.5.0", "tslib": "^2.6.2" } }, "sha512-B8oFNFTDV0j1yiJiqzkC2ybml+theNnmsLrTLBhJbnBLWkxEcmVGKVIMnATW9BUCBhHmEtDiogdNIzSwP8tbMw=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg=="], + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.899.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.899.0", "@aws-sdk/credential-provider-http": "3.899.0", "@aws-sdk/credential-provider-ini": "3.899.0", "@aws-sdk/credential-provider-process": "3.899.0", "@aws-sdk/credential-provider-sso": "3.899.0", "@aws-sdk/credential-provider-web-identity": "3.899.0", "@aws-sdk/types": "3.893.0", "@smithy/credential-provider-imds": "^4.1.2", "@smithy/property-provider": "^4.1.1", "@smithy/shared-ini-file-loader": "^4.2.0", "@smithy/types": "^4.5.0", "tslib": "^2.6.2" } }, "sha512-nHBnZ2ZCOqTGJ2A9xpVj8iK6+WV+j0JNv3XGEkIuL4mqtGEPJlEex/0mD/hqc1VF8wZzojji2OQ3892m1mUOSA=="], "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.899.0", "", { "dependencies": { "@aws-sdk/core": "3.899.0", "@aws-sdk/types": "3.893.0", "@smithy/property-provider": "^4.1.1", "@smithy/shared-ini-file-loader": "^4.2.0", "@smithy/types": "^4.5.0", "tslib": "^2.6.2" } }, "sha512-1PWSejKcJQUKBNPIqSHlEW4w8vSjmb+3kNJqCinJybjp5uP5BJgBp6QNcb8Nv30VBM0bn3ajVd76LCq4ZshQAw=="], @@ -1367,6 +1373,8 @@ "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], + "@types/nodemailer": ["@types/nodemailer@7.0.4", "", { "dependencies": { "@aws-sdk/client-sesv2": "^3.839.0", "@types/node": "*" } }, "sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow=="], + "@types/papaparse": ["@types/papaparse@5.3.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg=="], "@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="], @@ -2497,6 +2505,8 @@ "node-rsa": ["node-rsa@1.1.1", "", { "dependencies": { "asn1": "^0.2.4" } }, "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw=="], + "nodemailer": ["nodemailer@7.0.11", "", {}, "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw=="], + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="], @@ -3241,6 +3251,94 @@ "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-sdk/client-sesv2/@aws-sdk/core": ["@aws-sdk/core@3.940.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.940.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.940.0", "@aws-sdk/credential-provider-http": "3.940.0", "@aws-sdk/credential-provider-ini": "3.940.0", "@aws-sdk/credential-provider-process": "3.940.0", "@aws-sdk/credential-provider-sso": "3.940.0", "@aws-sdk/credential-provider-web-identity": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.940.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.940.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A=="], + + "@aws-sdk/client-sesv2/@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="], + + "@aws-sdk/client-sesv2/@smithy/core": ["@smithy/core@3.18.5", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.6", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw=="], + + "@aws-sdk/client-sesv2/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="], + + "@aws-sdk/client-sesv2/@smithy/hash-node": ["@smithy/hash-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA=="], + + "@aws-sdk/client-sesv2/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.12", "", { "dependencies": { "@smithy/core": "^3.18.5", "@smithy/middleware-serde": "^4.2.6", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/service-error-classification": "^4.2.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="], + + "@aws-sdk/client-sesv2/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="], + + "@aws-sdk/client-sesv2/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="], + + "@aws-sdk/client-sesv2/@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="], + + "@aws-sdk/client-sesv2/@smithy/smithy-client": ["@smithy/smithy-client@4.9.8", "", { "dependencies": { "@smithy/core": "^3.18.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA=="], + + "@aws-sdk/client-sesv2/@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="], + + "@aws-sdk/client-sesv2/@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="], + + "@aws-sdk/client-sesv2/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + + "@aws-sdk/client-sesv2/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + + "@aws-sdk/client-sesv2/@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.11", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.14", "", { "dependencies": { "@smithy/config-resolver": "^4.4.3", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA=="], + + "@aws-sdk/client-sesv2/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A=="], + + "@aws-sdk/client-sesv2/@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="], + + "@aws-sdk/client-sesv2/@smithy/util-retry": ["@smithy/util-retry@4.2.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg=="], + + "@aws-sdk/client-sesv2/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core": ["@aws-sdk/core@3.940.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], + + "@aws-sdk/credential-provider-login/@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], + + "@aws-sdk/credential-provider-login/@smithy/protocol-http": ["@smithy/protocol-http@5.3.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ=="], + + "@aws-sdk/credential-provider-login/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="], + + "@aws-sdk/credential-provider-login/@smithy/types": ["@smithy/types@4.9.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA=="], + "@azure/communication-email/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], "@azure/communication-email/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], @@ -3445,6 +3543,8 @@ "@types/node-fetch/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], + "@types/nodemailer/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], + "@types/papaparse/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], "@types/through/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], @@ -3701,6 +3801,154 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@aws-sdk/client-sesv2/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-env": "3.940.0", "@aws-sdk/credential-provider-http": "3.940.0", "@aws-sdk/credential-provider-login": "3.940.0", "@aws-sdk/credential-provider-process": "3.940.0", "@aws-sdk/credential-provider-sso": "3.940.0", "@aws-sdk/credential-provider-web-identity": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.940.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.940.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/token-providers": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/middleware-recursion-detection/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.1", "", {}, "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="], + + "@aws-sdk/client-sesv2/@smithy/config-resolver/@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + + "@aws-sdk/client-sesv2/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@aws-sdk/client-sesv2/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + + "@aws-sdk/client-sesv2/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@aws-sdk/client-sesv2/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="], + + "@aws-sdk/client-sesv2/@smithy/middleware-retry/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + + "@aws-sdk/client-sesv2/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], + + "@aws-sdk/client-sesv2/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA=="], + + "@aws-sdk/client-sesv2/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="], + + "@aws-sdk/client-sesv2/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@aws-sdk/client-sesv2/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@aws-sdk/client-sesv2/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="], + + "@aws-sdk/client-sesv2/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="], + + "@aws-sdk/client-sesv2/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg=="], + + "@aws-sdk/client-sesv2/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="], + + "@aws-sdk/client-sesv2/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core": ["@smithy/core@3.18.5", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.6", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.5", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client": ["@smithy/smithy-client@4.9.8", "", { "dependencies": { "@smithy/core": "^3.18.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.940.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/config-resolver": ["@smithy/config-resolver@4.4.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/core": ["@smithy/core@3.18.5", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.6", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/hash-node": ["@smithy/hash-node@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.5", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.12", "", { "dependencies": { "@smithy/core": "^3.18.5", "@smithy/middleware-serde": "^4.2.6", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/service-error-classification": "^4.2.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/smithy-client": ["@smithy/smithy-client@4.9.8", "", { "dependencies": { "@smithy/core": "^3.18.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-stack": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.11", "", { "dependencies": { "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.14", "", { "dependencies": { "@smithy/config-resolver": "^4.4.3", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-middleware": ["@smithy/util-middleware@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-retry": ["@smithy/util-retry@4.2.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], + "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "@browserbasehq/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], @@ -3799,6 +4047,8 @@ "@types/node-fetch/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + "@types/nodemailer/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + "@types/papaparse/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], "@types/through/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], @@ -3941,6 +4191,106 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/client-sesv2/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/client-sesv2/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/client-sesv2/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/client-sesv2/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-sesv2/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/client-sesv2/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/client-sesv2/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/client-sesv2/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-sesv2/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/signature-v4/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/signature-v4/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.3.12", "", { "dependencies": { "@smithy/core": "^3.18.5", "@smithy/middleware-serde": "^4.2.6", "@smithy/node-config-provider": "^4.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/middleware-recursion-detection/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.1", "", {}, "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/config-resolver/@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/core/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/middleware-retry/@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream": ["@smithy/util-stream@4.5.6", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0" } }, "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + "@browserbasehq/sdk/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "@browserbasehq/sdk/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], @@ -4029,6 +4379,62 @@ "test-exclude/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/client-sesv2/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-sesv2/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser": ["@smithy/url-parser@4.2.5", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.6", "", { "dependencies": { "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/querystring-builder": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/hash-node/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-base64/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "@trigger.dev/core/socket.io/engine.io/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], "lint-staged/listr2/cli-truncate/string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], @@ -4049,8 +4455,42 @@ "sim/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@aws-sdk/client-sesv2/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/client-sesv2/@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.5", "", { "dependencies": { "@smithy/types": "^4.9.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/core/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@smithy/smithy-client/@smithy/util-stream/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], + "lint-staged/listr2/cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "lint-staged/listr2/log-update/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/core/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], + + "@aws-sdk/credential-provider-login/@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], } } diff --git a/package.json b/package.json index 60219b77883..510943d58b8 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "fluent-ffmpeg": "2.1.3", "mongodb": "6.19.0", "neo4j-driver": "6.0.1", + "nodemailer": "7.0.11", "onedollarstats": "0.0.10", "postgres": "^3.4.5", "remark-gfm": "4.0.1", @@ -57,6 +58,7 @@ "@next/env": "15.4.1", "@octokit/rest": "^21.0.0", "@tailwindcss/typography": "0.5.19", + "@types/nodemailer": "7.0.4", "drizzle-kit": "^0.31.4", "husky": "9.1.7", "lint-staged": "16.0.0", From 741384d4540b976434dc59ff252dc7ec13c366f5 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 29 Nov 2025 13:54:01 -0800 Subject: [PATCH 3/4] added top level mocks for sporadically failing tests --- .../app/api/chat/[identifier]/route.test.ts | 90 +++++++++++-------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/apps/sim/app/api/chat/[identifier]/route.test.ts b/apps/sim/app/api/chat/[identifier]/route.test.ts index f872e35ee6d..c7cc5754e46 100644 --- a/apps/sim/app/api/chat/[identifier]/route.test.ts +++ b/apps/sim/app/api/chat/[identifier]/route.test.ts @@ -6,6 +6,23 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createMockRequest } from '@/app/api/__test-utils__/utils' +const createMockStream = () => { + return new ReadableStream({ + start(controller) { + controller.enqueue( + new TextEncoder().encode('data: {"blockId":"agent-1","chunk":"Hello"}\n\n') + ) + controller.enqueue( + new TextEncoder().encode('data: {"blockId":"agent-1","chunk":" world"}\n\n') + ) + controller.enqueue( + new TextEncoder().encode('data: {"event":"final","data":{"success":true}}\n\n') + ) + controller.close() + }, + }) +} + vi.mock('@/lib/execution/preprocessing', () => ({ preprocessExecution: vi.fn().mockResolvedValue({ success: true, @@ -36,28 +53,37 @@ vi.mock('@/lib/logs/execution/logging-session', () => ({ })), })) -describe('Chat Identifier API Route', () => { - const createMockStream = () => { - return new ReadableStream({ - start(controller) { - controller.enqueue( - new TextEncoder().encode('data: {"blockId":"agent-1","chunk":"Hello"}\n\n') - ) - controller.enqueue( - new TextEncoder().encode('data: {"blockId":"agent-1","chunk":" world"}\n\n') - ) - controller.enqueue( - new TextEncoder().encode('data: {"event":"final","data":{"success":true}}\n\n') - ) - controller.close() - }, - }) - } +vi.mock('@/lib/workflows/streaming', () => ({ + createStreamingResponse: vi.fn().mockImplementation(async () => createMockStream()), +})) + +vi.mock('@/lib/utils', () => ({ + SSE_HEADERS: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'X-Accel-Buffering': 'no', + }, + generateRequestId: vi.fn().mockReturnValue('test-request-id'), +})) + +vi.mock('@/app/api/workflows/[id]/execute/route', () => ({ + createFilteredResult: vi.fn().mockImplementation((result: any) => ({ + ...result, + logs: undefined, + metadata: result.metadata + ? { + ...result.metadata, + workflowConnections: undefined, + } + : undefined, + })), +})) +describe('Chat Identifier API Route', () => { const mockAddCorsHeaders = vi.fn().mockImplementation((response) => response) const mockValidateChatAuth = vi.fn().mockResolvedValue({ authorized: true }) const mockSetChatAuthCookie = vi.fn() - const mockCreateStreamingResponse = vi.fn().mockResolvedValue(createMockStream()) const mockChatResult = [ { @@ -104,16 +130,6 @@ describe('Chat Identifier API Route', () => { validateAuthToken: vi.fn().mockReturnValue(true), })) - vi.doMock('@/lib/workflows/streaming', () => ({ - createStreamingResponse: mockCreateStreamingResponse, - SSE_HEADERS: { - 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', - Connection: 'keep-alive', - 'X-Accel-Buffering': 'no', - }, - })) - vi.doMock('@/lib/logs/console/logger', () => ({ createLogger: vi.fn().mockReturnValue({ debug: vi.fn(), @@ -372,6 +388,7 @@ describe('Chat Identifier API Route', () => { const params = Promise.resolve({ identifier: 'test-chat' }) const { POST } = await import('@/app/api/chat/[identifier]/route') + const { createStreamingResponse } = await import('@/lib/workflows/streaming') const response = await POST(req, { params }) @@ -380,7 +397,7 @@ describe('Chat Identifier API Route', () => { expect(response.headers.get('Cache-Control')).toBe('no-cache') expect(response.headers.get('Connection')).toBe('keep-alive') - expect(mockCreateStreamingResponse).toHaveBeenCalledWith( + expect(createStreamingResponse).toHaveBeenCalledWith( expect.objectContaining({ workflow: expect.objectContaining({ id: 'workflow-id', @@ -396,7 +413,7 @@ describe('Chat Identifier API Route', () => { }), }) ) - }) + }, 10000) it('should handle streaming response body correctly', async () => { const req = createMockRequest('POST', { input: 'Hello world' }) @@ -423,8 +440,9 @@ describe('Chat Identifier API Route', () => { }) it('should handle workflow execution errors gracefully', async () => { - const originalStreamingResponse = mockCreateStreamingResponse.getMockImplementation() - mockCreateStreamingResponse.mockImplementationOnce(async () => { + const { createStreamingResponse } = await import('@/lib/workflows/streaming') + const originalStreamingResponse = vi.mocked(createStreamingResponse).getMockImplementation() + vi.mocked(createStreamingResponse).mockImplementationOnce(async () => { throw new Error('Execution failed') }) @@ -442,7 +460,7 @@ describe('Chat Identifier API Route', () => { expect(data).toHaveProperty('message', 'Execution failed') if (originalStreamingResponse) { - mockCreateStreamingResponse.mockImplementation(originalStreamingResponse) + vi.mocked(createStreamingResponse).mockImplementation(originalStreamingResponse) } }) @@ -474,10 +492,11 @@ describe('Chat Identifier API Route', () => { const params = Promise.resolve({ identifier: 'test-chat' }) const { POST } = await import('@/app/api/chat/[identifier]/route') + const { createStreamingResponse } = await import('@/lib/workflows/streaming') await POST(req, { params }) - expect(mockCreateStreamingResponse).toHaveBeenCalledWith( + expect(createStreamingResponse).toHaveBeenCalledWith( expect.objectContaining({ input: expect.objectContaining({ input: 'Hello world', @@ -492,10 +511,11 @@ describe('Chat Identifier API Route', () => { const params = Promise.resolve({ identifier: 'test-chat' }) const { POST } = await import('@/app/api/chat/[identifier]/route') + const { createStreamingResponse } = await import('@/lib/workflows/streaming') await POST(req, { params }) - expect(mockCreateStreamingResponse).toHaveBeenCalledWith( + expect(createStreamingResponse).toHaveBeenCalledWith( expect.objectContaining({ input: expect.objectContaining({ input: 'Hello world', From 17b533a68822d4912d9372ade028d607f89ed61e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 29 Nov 2025 17:55:57 -0800 Subject: [PATCH 4/4] incr type safety --- apps/sim/app/api/tools/smtp/send/route.ts | 50 ++- apps/sim/tools/linkedin/get_profile.ts | 20 +- apps/sim/tools/linkedin/share_post.ts | 42 +- apps/sim/tools/linkedin/types.ts | 40 ++ apps/sim/tools/mailchimp/add_member.ts | 6 +- .../tools/mailchimp/add_or_update_member.ts | 6 +- .../sim/tools/mailchimp/add_segment_member.ts | 4 +- .../mailchimp/add_subscriber_to_automation.ts | 3 +- apps/sim/tools/mailchimp/create_audience.ts | 5 +- .../tools/mailchimp/create_batch_operation.ts | 3 +- apps/sim/tools/mailchimp/create_campaign.ts | 6 +- apps/sim/tools/mailchimp/create_interest.ts | 3 +- .../mailchimp/create_interest_category.ts | 3 +- .../tools/mailchimp/create_landing_page.ts | 5 +- .../sim/tools/mailchimp/create_merge_field.ts | 3 +- apps/sim/tools/mailchimp/create_segment.ts | 5 +- apps/sim/tools/mailchimp/get_audience.ts | 3 +- apps/sim/tools/mailchimp/get_audiences.ts | 3 +- apps/sim/tools/mailchimp/get_automation.ts | 3 +- apps/sim/tools/mailchimp/get_automations.ts | 3 +- .../tools/mailchimp/get_batch_operation.ts | 3 +- .../tools/mailchimp/get_batch_operations.ts | 3 +- apps/sim/tools/mailchimp/get_campaign.ts | 4 +- .../tools/mailchimp/get_campaign_content.ts | 4 +- .../tools/mailchimp/get_campaign_report.ts | 4 +- .../tools/mailchimp/get_campaign_reports.ts | 4 +- apps/sim/tools/mailchimp/get_campaigns.ts | 4 +- apps/sim/tools/mailchimp/get_interest.ts | 3 +- .../tools/mailchimp/get_interest_category.ts | 3 +- apps/sim/tools/mailchimp/get_landing_page.ts | 3 +- apps/sim/tools/mailchimp/get_landing_pages.ts | 3 +- apps/sim/tools/mailchimp/get_member.ts | 4 +- apps/sim/tools/mailchimp/get_member_tags.ts | 4 +- apps/sim/tools/mailchimp/get_members.ts | 4 +- apps/sim/tools/mailchimp/get_merge_fields.ts | 3 +- apps/sim/tools/mailchimp/get_segment.ts | 3 +- .../tools/mailchimp/get_segment_members.ts | 3 +- apps/sim/tools/mailchimp/get_template.ts | 3 +- apps/sim/tools/mailchimp/get_templates.ts | 3 +- .../tools/mailchimp/set_campaign_content.ts | 6 +- apps/sim/tools/mailchimp/types.ts | 373 +++++++++++++++++- apps/sim/tools/mailchimp/unarchive_member.ts | 4 +- apps/sim/tools/mailchimp/update_audience.ts | 5 +- apps/sim/tools/mailchimp/update_campaign.ts | 6 +- apps/sim/tools/mailchimp/update_interest.ts | 5 +- .../mailchimp/update_interest_category.ts | 5 +- .../tools/mailchimp/update_landing_page.ts | 5 +- apps/sim/tools/mailchimp/update_member.ts | 6 +- .../sim/tools/mailchimp/update_merge_field.ts | 5 +- apps/sim/tools/mailchimp/update_segment.ts | 5 +- apps/sim/tools/mailchimp/update_template.ts | 5 +- apps/sim/tools/mailgun/types.ts | 49 ++- apps/sim/tools/sendgrid/add_contact.ts | 11 +- .../tools/sendgrid/add_contacts_to_list.ts | 8 +- apps/sim/tools/sendgrid/create_list.ts | 6 +- apps/sim/tools/sendgrid/create_template.ts | 6 +- .../tools/sendgrid/create_template_version.ts | 8 +- apps/sim/tools/sendgrid/delete_contacts.ts | 4 +- apps/sim/tools/sendgrid/delete_list.ts | 2 +- apps/sim/tools/sendgrid/delete_template.ts | 2 +- apps/sim/tools/sendgrid/get_contact.ts | 6 +- apps/sim/tools/sendgrid/get_list.ts | 6 +- apps/sim/tools/sendgrid/get_template.ts | 6 +- apps/sim/tools/sendgrid/list_all_lists.ts | 6 +- apps/sim/tools/sendgrid/list_templates.ts | 9 +- .../sendgrid/remove_contacts_from_list.ts | 4 +- apps/sim/tools/sendgrid/search_contacts.ts | 9 +- apps/sim/tools/sendgrid/send_mail.ts | 11 +- apps/sim/tools/sendgrid/types.ts | 88 ++++- 69 files changed, 759 insertions(+), 193 deletions(-) diff --git a/apps/sim/app/api/tools/smtp/send/route.ts b/apps/sim/app/api/tools/smtp/send/route.ts index d9e02f783ea..49c4eb9b136 100644 --- a/apps/sim/app/api/tools/smtp/send/route.ts +++ b/apps/sim/app/api/tools/smtp/send/route.ts @@ -86,7 +86,7 @@ export async function POST(request: NextRequest) { ? `"${validatedData.fromName}" <${validatedData.from}>` : validatedData.from - const mailOptions: any = { + const mailOptions: nodemailer.SendMailOptions = { from: fromAddress, to: validatedData.to, subject: validatedData.subject, @@ -165,7 +165,7 @@ export async function POST(request: NextRequest) { to: validatedData.to, subject: validatedData.subject, }) - } catch (error: any) { + } catch (error: unknown) { if (error instanceof z.ZodError) { logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors }) return NextResponse.json( @@ -178,26 +178,42 @@ export async function POST(request: NextRequest) { ) } + // Type guard for error objects with code property + const isNodeError = (err: unknown): err is NodeJS.ErrnoException => { + return err instanceof Error && 'code' in err + } + let errorMessage = 'Failed to send email via SMTP' - if (error.code === 'EAUTH') { - errorMessage = 'SMTP authentication failed - check username and password' - } else if (error.code === 'ECONNECTION' || error.code === 'ECONNREFUSED') { - errorMessage = 'Could not connect to SMTP server - check host and port' - } else if (error.code === 'ECONNRESET') { - errorMessage = 'Connection was reset by SMTP server - check network or firewall settings' - } else if (error.code === 'ETIMEDOUT') { - errorMessage = 'SMTP server connection timeout - check host, port, and network' - } else if (error.responseCode && error.responseCode >= 500) { - errorMessage = 'SMTP server error - please try again later' - } else if (error.responseCode && error.responseCode >= 400) { - errorMessage = 'Email rejected by SMTP server - check recipient addresses' + if (isNodeError(error)) { + if (error.code === 'EAUTH') { + errorMessage = 'SMTP authentication failed - check username and password' + } else if (error.code === 'ECONNECTION' || error.code === 'ECONNREFUSED') { + errorMessage = 'Could not connect to SMTP server - check host and port' + } else if (error.code === 'ECONNRESET') { + errorMessage = 'Connection was reset by SMTP server' + } else if (error.code === 'ETIMEDOUT') { + errorMessage = 'SMTP server connection timeout' + } + } + + // Check for SMTP response codes + const hasResponseCode = (err: unknown): err is { responseCode: number } => { + return typeof err === 'object' && err !== null && 'responseCode' in err + } + + if (hasResponseCode(error)) { + if (error.responseCode >= 500) { + errorMessage = 'SMTP server error - please try again later' + } else if (error.responseCode >= 400) { + errorMessage = 'Email rejected by SMTP server - check recipient addresses' + } } logger.error(`[${requestId}] Error sending email via SMTP:`, { - error: error.message, - code: error.code, - responseCode: error.responseCode, + error: error instanceof Error ? error.message : String(error), + code: isNodeError(error) ? error.code : undefined, + responseCode: hasResponseCode(error) ? error.responseCode : undefined, }) return NextResponse.json( diff --git a/apps/sim/tools/linkedin/get_profile.ts b/apps/sim/tools/linkedin/get_profile.ts index 90b23daf27c..0c766df85e9 100644 --- a/apps/sim/tools/linkedin/get_profile.ts +++ b/apps/sim/tools/linkedin/get_profile.ts @@ -1,22 +1,6 @@ +import type { GetProfileParams, GetProfileResponse } from '@/tools/linkedin/types' import type { ToolConfig } from '@/tools/types' -interface GetProfileParams { - accessToken: string -} - -interface GetProfileResponse { - success: boolean - output: { - profile?: { - id: string - name: string - email?: string - picture?: string - } - } - error?: string -} - export const linkedInGetProfileTool: ToolConfig = { id: 'linkedin_get_profile', name: 'Get LinkedIn Profile', @@ -40,7 +24,7 @@ export const linkedInGetProfileTool: ToolConfig 'https://api.linkedin.com/v2/userinfo', method: 'GET', - headers: (params: GetProfileParams) => ({ + headers: (params: GetProfileParams): Record => ({ Authorization: `Bearer ${params.accessToken}`, 'X-Restli-Protocol-Version': '2.0.0', }), diff --git a/apps/sim/tools/linkedin/share_post.ts b/apps/sim/tools/linkedin/share_post.ts index 1ef99dbe165..ed3bd8d198f 100644 --- a/apps/sim/tools/linkedin/share_post.ts +++ b/apps/sim/tools/linkedin/share_post.ts @@ -1,17 +1,18 @@ +import type { + LinkedInProfileOutput, + ProfileIdExtractor, + SharePostParams, + SharePostResponse, +} from '@/tools/linkedin/types' import type { ToolConfig } from '@/tools/types' -interface SharePostParams { - text: string - visibility: 'PUBLIC' | 'CONNECTIONS' - accessToken: string -} - -interface SharePostResponse { - success: boolean - output: { - postId?: string +// Helper function to extract profile ID from various response formats +const extractProfileId: ProfileIdExtractor = (output: unknown): string | null => { + if (typeof output === 'object' && output !== null) { + const profileOutput = output as LinkedInProfileOutput + return profileOutput.profile?.id || profileOutput.sub || profileOutput.id || null } - error?: string + return null } export const linkedInSharePostTool: ToolConfig = { @@ -69,25 +70,18 @@ export const linkedInSharePostTool: ToolConfig string | null diff --git a/apps/sim/tools/mailchimp/add_member.ts b/apps/sim/tools/mailchimp/add_member.ts index 226ab6dcebe..7a917f00ca3 100644 --- a/apps/sim/tools/mailchimp/add_member.ts +++ b/apps/sim/tools/mailchimp/add_member.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types' const logger = createLogger('MailchimpAddMember') @@ -16,7 +16,7 @@ export interface MailchimpAddMemberParams { export interface MailchimpAddMemberResponse { success: boolean output: { - member: any + member: MailchimpMember metadata: { operation: 'add_member' subscriberHash: string @@ -81,7 +81,7 @@ export const mailchimpAddMemberTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = { + const body: Record = { email_address: params.emailAddress, status: params.status, } diff --git a/apps/sim/tools/mailchimp/add_or_update_member.ts b/apps/sim/tools/mailchimp/add_or_update_member.ts index ae5a7865447..a4b3daf7ac3 100644 --- a/apps/sim/tools/mailchimp/add_or_update_member.ts +++ b/apps/sim/tools/mailchimp/add_or_update_member.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types' const logger = createLogger('MailchimpAddOrUpdateMember') @@ -17,7 +17,7 @@ export interface MailchimpAddOrUpdateMemberParams { export interface MailchimpAddOrUpdateMemberResponse { success: boolean output: { - member: any + member: MailchimpMember metadata: { operation: 'add_or_update_member' subscriberHash: string @@ -89,7 +89,7 @@ export const mailchimpAddOrUpdateMemberTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = { + const body: Record = { email_address: params.emailAddress, status_if_new: params.statusIfNew, } diff --git a/apps/sim/tools/mailchimp/add_segment_member.ts b/apps/sim/tools/mailchimp/add_segment_member.ts index c5ea3d686a9..0ab705a2975 100644 --- a/apps/sim/tools/mailchimp/add_segment_member.ts +++ b/apps/sim/tools/mailchimp/add_segment_member.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types' const logger = createLogger('MailchimpAddSegmentMember') @@ -14,7 +14,7 @@ export interface MailchimpAddSegmentMemberParams { export interface MailchimpAddSegmentMemberResponse { success: boolean output: { - member: any + member: MailchimpMember metadata: { operation: 'add_segment_member' segmentId: string diff --git a/apps/sim/tools/mailchimp/add_subscriber_to_automation.ts b/apps/sim/tools/mailchimp/add_subscriber_to_automation.ts index 81152a89852..8d84ec807ad 100644 --- a/apps/sim/tools/mailchimp/add_subscriber_to_automation.ts +++ b/apps/sim/tools/mailchimp/add_subscriber_to_automation.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpMember } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpAddSubscriberToAutomation') @@ -14,7 +15,7 @@ export interface MailchimpAddSubscriberToAutomationParams { export interface MailchimpAddSubscriberToAutomationResponse { success: boolean output: { - subscriber: any + subscriber: MailchimpMember metadata: { operation: 'add_subscriber_to_automation' workflowId: string diff --git a/apps/sim/tools/mailchimp/create_audience.ts b/apps/sim/tools/mailchimp/create_audience.ts index 17310ec9321..bb3d827f753 100644 --- a/apps/sim/tools/mailchimp/create_audience.ts +++ b/apps/sim/tools/mailchimp/create_audience.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpAudience } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpCreateAudience') @@ -16,7 +17,7 @@ export interface MailchimpCreateAudienceParams { export interface MailchimpCreateAudienceResponse { success: boolean output: { - list: any + list: MailchimpAudience metadata: { operation: 'create_audience' listId: string @@ -81,7 +82,7 @@ export const mailchimpCreateAudienceTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = { + const body: Record = { name: params.audienceName, permission_reminder: params.permissionReminder, email_type_option: params.emailTypeOption === 'true', diff --git a/apps/sim/tools/mailchimp/create_batch_operation.ts b/apps/sim/tools/mailchimp/create_batch_operation.ts index 5e49f871b1b..1da6c56ff4d 100644 --- a/apps/sim/tools/mailchimp/create_batch_operation.ts +++ b/apps/sim/tools/mailchimp/create_batch_operation.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpBatchOperation } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpCreateBatchOperation') @@ -12,7 +13,7 @@ export interface MailchimpCreateBatchOperationParams { export interface MailchimpCreateBatchOperationResponse { success: boolean output: { - batch: any + batch: MailchimpBatchOperation metadata: { operation: 'create_batch_operation' batchId: string diff --git a/apps/sim/tools/mailchimp/create_campaign.ts b/apps/sim/tools/mailchimp/create_campaign.ts index add7e5dce8b..fce2b86fee9 100644 --- a/apps/sim/tools/mailchimp/create_campaign.ts +++ b/apps/sim/tools/mailchimp/create_campaign.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaign } from './types' const logger = createLogger('MailchimpCreateCampaign') @@ -14,7 +14,7 @@ export interface MailchimpCreateCampaignParams { export interface MailchimpCreateCampaignResponse { success: boolean output: { - campaign: any + campaign: MailchimpCampaign metadata: { operation: 'create_campaign' campaignId: string @@ -67,7 +67,7 @@ export const mailchimpCreateCampaignTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = { + const body: Record = { type: params.campaignType, } diff --git a/apps/sim/tools/mailchimp/create_interest.ts b/apps/sim/tools/mailchimp/create_interest.ts index 2845a347c4a..c29c562bee6 100644 --- a/apps/sim/tools/mailchimp/create_interest.ts +++ b/apps/sim/tools/mailchimp/create_interest.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpInterest } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpCreateInterest') @@ -14,7 +15,7 @@ export interface MailchimpCreateInterestParams { export interface MailchimpCreateInterestResponse { success: boolean output: { - interest: any + interest: MailchimpInterest metadata: { operation: 'create_interest' interestId: string diff --git a/apps/sim/tools/mailchimp/create_interest_category.ts b/apps/sim/tools/mailchimp/create_interest_category.ts index 2fb3b147b47..f33a6e73426 100644 --- a/apps/sim/tools/mailchimp/create_interest_category.ts +++ b/apps/sim/tools/mailchimp/create_interest_category.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpInterestCategory } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpCreateInterestCategory') @@ -14,7 +15,7 @@ export interface MailchimpCreateInterestCategoryParams { export interface MailchimpCreateInterestCategoryResponse { success: boolean output: { - category: any + category: MailchimpInterestCategory metadata: { operation: 'create_interest_category' interestCategoryId: string diff --git a/apps/sim/tools/mailchimp/create_landing_page.ts b/apps/sim/tools/mailchimp/create_landing_page.ts index 5ed9ca3f21d..c2895c8cc4e 100644 --- a/apps/sim/tools/mailchimp/create_landing_page.ts +++ b/apps/sim/tools/mailchimp/create_landing_page.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpLandingPage } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpCreateLandingPage') @@ -13,7 +14,7 @@ export interface MailchimpCreateLandingPageParams { export interface MailchimpCreateLandingPageResponse { success: boolean output: { - landingPage: any + landingPage: MailchimpLandingPage metadata: { operation: 'create_landing_page' pageId: string @@ -60,7 +61,7 @@ export const mailchimpCreateLandingPageTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = { + const body: Record = { type: params.landingPageType, } diff --git a/apps/sim/tools/mailchimp/create_merge_field.ts b/apps/sim/tools/mailchimp/create_merge_field.ts index d069404ab2a..d409c96ecb6 100644 --- a/apps/sim/tools/mailchimp/create_merge_field.ts +++ b/apps/sim/tools/mailchimp/create_merge_field.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpMergeField } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpCreateMergeField') @@ -14,7 +15,7 @@ export interface MailchimpCreateMergeFieldParams { export interface MailchimpCreateMergeFieldResponse { success: boolean output: { - mergeField: any + mergeField: MailchimpMergeField metadata: { operation: 'create_merge_field' mergeId: string diff --git a/apps/sim/tools/mailchimp/create_segment.ts b/apps/sim/tools/mailchimp/create_segment.ts index 4c3afb767a0..c278444806a 100644 --- a/apps/sim/tools/mailchimp/create_segment.ts +++ b/apps/sim/tools/mailchimp/create_segment.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpSegment } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpCreateSegment') @@ -14,7 +15,7 @@ export interface MailchimpCreateSegmentParams { export interface MailchimpCreateSegmentResponse { success: boolean output: { - segment: any + segment: MailchimpSegment metadata: { operation: 'create_segment' segmentId: string @@ -67,7 +68,7 @@ export const mailchimpCreateSegmentTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = { + const body: Record = { name: params.segmentName, } diff --git a/apps/sim/tools/mailchimp/get_audience.ts b/apps/sim/tools/mailchimp/get_audience.ts index daf71dd4b25..c4f92bc6beb 100644 --- a/apps/sim/tools/mailchimp/get_audience.ts +++ b/apps/sim/tools/mailchimp/get_audience.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpAudience } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetAudience') @@ -12,7 +13,7 @@ export interface MailchimpGetAudienceParams { export interface MailchimpGetAudienceResponse { success: boolean output: { - list: any + list: MailchimpAudience metadata: { operation: 'get_audience' listId: string diff --git a/apps/sim/tools/mailchimp/get_audiences.ts b/apps/sim/tools/mailchimp/get_audiences.ts index 971d7a528d7..cf9383c96a1 100644 --- a/apps/sim/tools/mailchimp/get_audiences.ts +++ b/apps/sim/tools/mailchimp/get_audiences.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpAudience } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetAudiences') @@ -13,7 +14,7 @@ export interface MailchimpGetAudiencesParams { export interface MailchimpGetAudiencesResponse { success: boolean output: { - lists: any[] + lists: MailchimpAudience[] totalItems: number metadata: { operation: 'get_audiences' diff --git a/apps/sim/tools/mailchimp/get_automation.ts b/apps/sim/tools/mailchimp/get_automation.ts index 9b05f9f5ea7..dae107aa633 100644 --- a/apps/sim/tools/mailchimp/get_automation.ts +++ b/apps/sim/tools/mailchimp/get_automation.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpAutomation } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetAutomation') @@ -12,7 +13,7 @@ export interface MailchimpGetAutomationParams { export interface MailchimpGetAutomationResponse { success: boolean output: { - automation: any + automation: MailchimpAutomation metadata: { operation: 'get_automation' workflowId: string diff --git a/apps/sim/tools/mailchimp/get_automations.ts b/apps/sim/tools/mailchimp/get_automations.ts index ea47a871335..4ae25e30db7 100644 --- a/apps/sim/tools/mailchimp/get_automations.ts +++ b/apps/sim/tools/mailchimp/get_automations.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpAutomation } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetAutomations') @@ -13,7 +14,7 @@ export interface MailchimpGetAutomationsParams { export interface MailchimpGetAutomationsResponse { success: boolean output: { - automations: any[] + automations: MailchimpAutomation[] totalItems: number metadata: { operation: 'get_automations' diff --git a/apps/sim/tools/mailchimp/get_batch_operation.ts b/apps/sim/tools/mailchimp/get_batch_operation.ts index 4e1ccdd089d..a9c478d74dc 100644 --- a/apps/sim/tools/mailchimp/get_batch_operation.ts +++ b/apps/sim/tools/mailchimp/get_batch_operation.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpBatchOperation } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetBatchOperation') @@ -12,7 +13,7 @@ export interface MailchimpGetBatchOperationParams { export interface MailchimpGetBatchOperationResponse { success: boolean output: { - batch: any + batch: MailchimpBatchOperation metadata: { operation: 'get_batch_operation' batchId: string diff --git a/apps/sim/tools/mailchimp/get_batch_operations.ts b/apps/sim/tools/mailchimp/get_batch_operations.ts index 0e98715d869..c4ef501ef84 100644 --- a/apps/sim/tools/mailchimp/get_batch_operations.ts +++ b/apps/sim/tools/mailchimp/get_batch_operations.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpBatchOperation } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetBatchOperations') @@ -13,7 +14,7 @@ export interface MailchimpGetBatchOperationsParams { export interface MailchimpGetBatchOperationsResponse { success: boolean output: { - batches: any[] + batches: MailchimpBatchOperation[] totalItems: number metadata: { operation: 'get_batch_operations' diff --git a/apps/sim/tools/mailchimp/get_campaign.ts b/apps/sim/tools/mailchimp/get_campaign.ts index ec40e0272ff..4c4ac685421 100644 --- a/apps/sim/tools/mailchimp/get_campaign.ts +++ b/apps/sim/tools/mailchimp/get_campaign.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaign } from './types' const logger = createLogger('MailchimpGetCampaign') @@ -12,7 +12,7 @@ export interface MailchimpGetCampaignParams { export interface MailchimpGetCampaignResponse { success: boolean output: { - campaign: any + campaign: MailchimpCampaign metadata: { operation: 'get_campaign' campaignId: string diff --git a/apps/sim/tools/mailchimp/get_campaign_content.ts b/apps/sim/tools/mailchimp/get_campaign_content.ts index 706b8b5ae7f..3e0a33bf5ef 100644 --- a/apps/sim/tools/mailchimp/get_campaign_content.ts +++ b/apps/sim/tools/mailchimp/get_campaign_content.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaignContent } from './types' const logger = createLogger('MailchimpGetCampaignContent') @@ -12,7 +12,7 @@ export interface MailchimpGetCampaignContentParams { export interface MailchimpGetCampaignContentResponse { success: boolean output: { - content: any + content: MailchimpCampaignContent metadata: { operation: 'get_campaign_content' campaignId: string diff --git a/apps/sim/tools/mailchimp/get_campaign_report.ts b/apps/sim/tools/mailchimp/get_campaign_report.ts index 852a9c912f8..306baef7cc8 100644 --- a/apps/sim/tools/mailchimp/get_campaign_report.ts +++ b/apps/sim/tools/mailchimp/get_campaign_report.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaignReport } from './types' const logger = createLogger('MailchimpGetCampaignReport') @@ -12,7 +12,7 @@ export interface MailchimpGetCampaignReportParams { export interface MailchimpGetCampaignReportResponse { success: boolean output: { - report: any + report: MailchimpCampaignReport metadata: { operation: 'get_campaign_report' campaignId: string diff --git a/apps/sim/tools/mailchimp/get_campaign_reports.ts b/apps/sim/tools/mailchimp/get_campaign_reports.ts index d83a2acd2c5..d5a1bd152ae 100644 --- a/apps/sim/tools/mailchimp/get_campaign_reports.ts +++ b/apps/sim/tools/mailchimp/get_campaign_reports.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaignReport } from './types' const logger = createLogger('MailchimpGetCampaignReports') @@ -13,7 +13,7 @@ export interface MailchimpGetCampaignReportsParams { export interface MailchimpGetCampaignReportsResponse { success: boolean output: { - reports: any[] + reports: MailchimpCampaignReport[] totalItems: number metadata: { operation: 'get_campaign_reports' diff --git a/apps/sim/tools/mailchimp/get_campaigns.ts b/apps/sim/tools/mailchimp/get_campaigns.ts index bc725ab7bcc..65b54042406 100644 --- a/apps/sim/tools/mailchimp/get_campaigns.ts +++ b/apps/sim/tools/mailchimp/get_campaigns.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaign } from './types' const logger = createLogger('MailchimpGetCampaigns') @@ -15,7 +15,7 @@ export interface MailchimpGetCampaignsParams { export interface MailchimpGetCampaignsResponse { success: boolean output: { - campaigns: any[] + campaigns: MailchimpCampaign[] totalItems: number metadata: { operation: 'get_campaigns' diff --git a/apps/sim/tools/mailchimp/get_interest.ts b/apps/sim/tools/mailchimp/get_interest.ts index bdde022c769..8e1b1bea1df 100644 --- a/apps/sim/tools/mailchimp/get_interest.ts +++ b/apps/sim/tools/mailchimp/get_interest.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpInterest } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetInterest') @@ -14,7 +15,7 @@ export interface MailchimpGetInterestParams { export interface MailchimpGetInterestResponse { success: boolean output: { - interest: any + interest: MailchimpInterest metadata: { operation: 'get_interest' interestId: string diff --git a/apps/sim/tools/mailchimp/get_interest_category.ts b/apps/sim/tools/mailchimp/get_interest_category.ts index 6d51d0457f6..e550a3e3b60 100644 --- a/apps/sim/tools/mailchimp/get_interest_category.ts +++ b/apps/sim/tools/mailchimp/get_interest_category.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpInterestCategory } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetInterestCategory') @@ -13,7 +14,7 @@ export interface MailchimpGetInterestCategoryParams { export interface MailchimpGetInterestCategoryResponse { success: boolean output: { - category: any + category: MailchimpInterestCategory metadata: { operation: 'get_interest_category' interestCategoryId: string diff --git a/apps/sim/tools/mailchimp/get_landing_page.ts b/apps/sim/tools/mailchimp/get_landing_page.ts index f3ec0cf37b7..c8abd6265e0 100644 --- a/apps/sim/tools/mailchimp/get_landing_page.ts +++ b/apps/sim/tools/mailchimp/get_landing_page.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpLandingPage } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetLandingPage') @@ -12,7 +13,7 @@ export interface MailchimpGetLandingPageParams { export interface MailchimpGetLandingPageResponse { success: boolean output: { - landingPage: any + landingPage: MailchimpLandingPage metadata: { operation: 'get_landing_page' pageId: string diff --git a/apps/sim/tools/mailchimp/get_landing_pages.ts b/apps/sim/tools/mailchimp/get_landing_pages.ts index b8095cdd9c6..7147e5d0053 100644 --- a/apps/sim/tools/mailchimp/get_landing_pages.ts +++ b/apps/sim/tools/mailchimp/get_landing_pages.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpLandingPage } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetLandingPages') @@ -13,7 +14,7 @@ export interface MailchimpGetLandingPagesParams { export interface MailchimpGetLandingPagesResponse { success: boolean output: { - landingPages: any[] + landingPages: MailchimpLandingPage[] totalItems: number metadata: { operation: 'get_landing_pages' diff --git a/apps/sim/tools/mailchimp/get_member.ts b/apps/sim/tools/mailchimp/get_member.ts index 42a24a1eaa9..8be49b13c2a 100644 --- a/apps/sim/tools/mailchimp/get_member.ts +++ b/apps/sim/tools/mailchimp/get_member.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types' const logger = createLogger('MailchimpGetMember') @@ -13,7 +13,7 @@ export interface MailchimpGetMemberParams { export interface MailchimpGetMemberResponse { success: boolean output: { - member: any + member: MailchimpMember metadata: { operation: 'get_member' subscriberHash: string diff --git a/apps/sim/tools/mailchimp/get_member_tags.ts b/apps/sim/tools/mailchimp/get_member_tags.ts index 850f7e23b93..f4816fe7783 100644 --- a/apps/sim/tools/mailchimp/get_member_tags.ts +++ b/apps/sim/tools/mailchimp/get_member_tags.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpTag } from './types' const logger = createLogger('MailchimpGetMemberTags') @@ -13,7 +13,7 @@ export interface MailchimpGetMemberTagsParams { export interface MailchimpGetMemberTagsResponse { success: boolean output: { - tags: any[] + tags: MailchimpTag[] totalItems: number metadata: { operation: 'get_member_tags' diff --git a/apps/sim/tools/mailchimp/get_members.ts b/apps/sim/tools/mailchimp/get_members.ts index 64fbaa7b3ae..a7e31dcc3ff 100644 --- a/apps/sim/tools/mailchimp/get_members.ts +++ b/apps/sim/tools/mailchimp/get_members.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types' const logger = createLogger('MailchimpGetMembers') @@ -15,7 +15,7 @@ export interface MailchimpGetMembersParams { export interface MailchimpGetMembersResponse { success: boolean output: { - members: any[] + members: MailchimpMember[] totalItems: number metadata: { operation: 'get_members' diff --git a/apps/sim/tools/mailchimp/get_merge_fields.ts b/apps/sim/tools/mailchimp/get_merge_fields.ts index 8e1dbca19a7..796429f03d7 100644 --- a/apps/sim/tools/mailchimp/get_merge_fields.ts +++ b/apps/sim/tools/mailchimp/get_merge_fields.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpMergeField } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetMergeFields') @@ -14,7 +15,7 @@ export interface MailchimpGetMergeFieldsParams { export interface MailchimpGetMergeFieldsResponse { success: boolean output: { - mergeFields: any[] + mergeFields: MailchimpMergeField[] totalItems: number metadata: { operation: 'get_merge_fields' diff --git a/apps/sim/tools/mailchimp/get_segment.ts b/apps/sim/tools/mailchimp/get_segment.ts index ed2ba417ad2..171e04bb8a0 100644 --- a/apps/sim/tools/mailchimp/get_segment.ts +++ b/apps/sim/tools/mailchimp/get_segment.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpSegment } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetSegment') @@ -13,7 +14,7 @@ export interface MailchimpGetSegmentParams { export interface MailchimpGetSegmentResponse { success: boolean output: { - segment: any + segment: MailchimpSegment metadata: { operation: 'get_segment' segmentId: string diff --git a/apps/sim/tools/mailchimp/get_segment_members.ts b/apps/sim/tools/mailchimp/get_segment_members.ts index 8a594c8fb27..4c519597305 100644 --- a/apps/sim/tools/mailchimp/get_segment_members.ts +++ b/apps/sim/tools/mailchimp/get_segment_members.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpMember } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetSegmentMembers') @@ -15,7 +16,7 @@ export interface MailchimpGetSegmentMembersParams { export interface MailchimpGetSegmentMembersResponse { success: boolean output: { - members: any[] + members: MailchimpMember[] totalItems: number metadata: { operation: 'get_segment_members' diff --git a/apps/sim/tools/mailchimp/get_template.ts b/apps/sim/tools/mailchimp/get_template.ts index 31a4f793896..9e079a23e4c 100644 --- a/apps/sim/tools/mailchimp/get_template.ts +++ b/apps/sim/tools/mailchimp/get_template.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpTemplate } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetTemplate') @@ -12,7 +13,7 @@ export interface MailchimpGetTemplateParams { export interface MailchimpGetTemplateResponse { success: boolean output: { - template: any + template: MailchimpTemplate metadata: { operation: 'get_template' templateId: string diff --git a/apps/sim/tools/mailchimp/get_templates.ts b/apps/sim/tools/mailchimp/get_templates.ts index db181474c66..8be121bf439 100644 --- a/apps/sim/tools/mailchimp/get_templates.ts +++ b/apps/sim/tools/mailchimp/get_templates.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpTemplate } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpGetTemplates') @@ -13,7 +14,7 @@ export interface MailchimpGetTemplatesParams { export interface MailchimpGetTemplatesResponse { success: boolean output: { - templates: any[] + templates: MailchimpTemplate[] totalItems: number metadata: { operation: 'get_templates' diff --git a/apps/sim/tools/mailchimp/set_campaign_content.ts b/apps/sim/tools/mailchimp/set_campaign_content.ts index 737756c5484..9dcf0a9d82c 100644 --- a/apps/sim/tools/mailchimp/set_campaign_content.ts +++ b/apps/sim/tools/mailchimp/set_campaign_content.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaignContent } from './types' const logger = createLogger('MailchimpSetCampaignContent') @@ -15,7 +15,7 @@ export interface MailchimpSetCampaignContentParams { export interface MailchimpSetCampaignContentResponse { success: boolean output: { - content: any + content: MailchimpCampaignContent metadata: { operation: 'set_campaign_content' campaignId: string @@ -74,7 +74,7 @@ export const mailchimpSetCampaignContentTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.html) body.html = params.html if (params.plainText) body.plain_text = params.plainText diff --git a/apps/sim/tools/mailchimp/types.ts b/apps/sim/tools/mailchimp/types.ts index 2348e686393..7fcdb9d5d83 100644 --- a/apps/sim/tools/mailchimp/types.ts +++ b/apps/sim/tools/mailchimp/types.ts @@ -23,12 +23,377 @@ export interface MailchimpResponse { paging?: MailchimpPagingInfo metadata: { operation: string - [key: string]: any + [key: string]: unknown } success: boolean } } +// Member/Subscriber +export interface MailchimpMember { + id: string + email_address: string + unique_email_id?: string + status: 'subscribed' | 'unsubscribed' | 'cleaned' | 'pending' + merge_fields?: Record + interests?: Record + stats?: { + avg_open_rate?: number + avg_click_rate?: number + } + ip_signup?: string + timestamp_signup?: string + ip_opt?: string + timestamp_opt?: string + member_rating?: number + last_changed?: string + language?: string + vip?: boolean + email_client?: string + location?: { + latitude?: number + longitude?: number + gmtoff?: number + dstoff?: number + country_code?: string + timezone?: string + } + tags?: Array<{ id: number; name: string }> + [key: string]: unknown +} + +// Audience/List +export interface MailchimpAudience { + id: string + name: string + contact: { + company: string + address1: string + address2?: string + city: string + state: string + zip: string + country: string + phone?: string + } + permission_reminder: string + campaign_defaults: { + from_name: string + from_email: string + subject: string + language: string + } + email_type_option: boolean + stats?: { + member_count?: number + unsubscribe_count?: number + cleaned_count?: number + member_count_since_send?: number + unsubscribe_count_since_send?: number + cleaned_count_since_send?: number + campaign_count?: number + campaign_last_sent?: string + merge_field_count?: number + avg_sub_rate?: number + avg_unsub_rate?: number + target_sub_rate?: number + open_rate?: number + click_rate?: number + last_sub_date?: string + last_unsub_date?: string + } + date_created?: string + list_rating?: number + subscribe_url_short?: string + subscribe_url_long?: string + visibility?: string + [key: string]: unknown +} + +// Campaign +export interface MailchimpCampaign { + id: string + type: 'regular' | 'plaintext' | 'absplit' | 'rss' | 'variate' + create_time?: string + archive_url?: string + long_archive_url?: string + status: 'save' | 'paused' | 'schedule' | 'sending' | 'sent' + emails_sent?: number + send_time?: string + content_type?: string + recipients?: { + list_id: string + list_name?: string + segment_text?: string + recipient_count?: number + } + settings?: { + subject_line?: string + preview_text?: string + title?: string + from_name?: string + reply_to?: string + use_conversation?: boolean + to_name?: string + folder_id?: string + authenticate?: boolean + auto_footer?: boolean + inline_css?: boolean + auto_tweet?: boolean + fb_comments?: boolean + timewarp?: boolean + template_id?: number + drag_and_drop?: boolean + } + tracking?: { + opens?: boolean + html_clicks?: boolean + text_clicks?: boolean + goal_tracking?: boolean + ecomm360?: boolean + google_analytics?: string + clicktale?: string + } + [key: string]: unknown +} + +// Campaign Content +export interface MailchimpCampaignContent { + html?: string + plain_text?: string + archive_html?: string + [key: string]: unknown +} + +// Campaign Report +export interface MailchimpCampaignReport { + id: string + campaign_title?: string + type?: string + emails_sent?: number + abuse_reports?: number + unsubscribed?: number + send_time?: string + bounces?: { + hard_bounces?: number + soft_bounces?: number + syntax_errors?: number + } + forwards?: { + forwards_count?: number + forwards_opens?: number + } + opens?: { + opens_total?: number + unique_opens?: number + open_rate?: number + last_open?: string + } + clicks?: { + clicks_total?: number + unique_clicks?: number + unique_subscriber_clicks?: number + click_rate?: number + last_click?: string + } + list_stats?: { + sub_rate?: number + unsub_rate?: number + open_rate?: number + click_rate?: number + } + [key: string]: unknown +} + +// Automation +export interface MailchimpAutomation { + id: string + create_time?: string + start_time?: string + status: 'save' | 'paused' | 'sending' + emails_sent?: number + recipients?: { + list_id: string + list_name?: string + segment_opts?: unknown + } + settings?: { + title?: string + from_name?: string + reply_to?: string + use_conversation?: boolean + to_name?: string + authenticate?: boolean + auto_footer?: boolean + inline_css?: boolean + } + tracking?: { + opens?: boolean + html_clicks?: boolean + text_clicks?: boolean + goal_tracking?: boolean + ecomm360?: boolean + google_analytics?: string + clicktale?: string + } + [key: string]: unknown +} + +// Segment +export interface MailchimpSegment { + id: number + name: string + member_count?: number + type: 'saved' | 'static' | 'fuzzy' + created_at?: string + updated_at?: string + options?: { + match?: 'any' | 'all' + conditions?: Array<{ + condition_type?: string + field?: string + op?: string + value?: unknown + }> + } + list_id?: string + [key: string]: unknown +} + +// Template +export interface MailchimpTemplate { + id: number + type: string + name: string + drag_and_drop?: boolean + responsive?: boolean + category?: string + date_created?: string + date_edited?: string + created_by?: string + edited_by?: string + active?: boolean + folder_id?: string + thumbnail?: string + share_url?: string + [key: string]: unknown +} + +// Landing Page +export interface MailchimpLandingPage { + id: string + name: string + title?: string + description?: string + template_id?: number + status: 'draft' | 'published' | 'unpublished' + list_id?: string + store_id?: string + web_id?: number + created_at?: string + published_at?: string + unpublished_at?: string + updated_at?: string + url?: string + tracking?: { + opens?: boolean + html_clicks?: boolean + text_clicks?: boolean + goal_tracking?: boolean + ecomm360?: boolean + google_analytics?: string + clicktale?: string + } + [key: string]: unknown +} + +// Interest Category +export interface MailchimpInterestCategory { + list_id?: string + id: string + title: string + display_order?: number + type: 'checkboxes' | 'dropdown' | 'radio' | 'hidden' + [key: string]: unknown +} + +// Interest +export interface MailchimpInterest { + category_id?: string + list_id?: string + id: string + name: string + subscriber_count?: string + display_order?: number + [key: string]: unknown +} + +// Merge Field +export interface MailchimpMergeField { + merge_id?: number + tag: string + name: string + type: + | 'text' + | 'number' + | 'address' + | 'phone' + | 'date' + | 'url' + | 'imageurl' + | 'radio' + | 'dropdown' + | 'birthday' + | 'zip' + required?: boolean + default_value?: string + public?: boolean + display_order?: number + options?: { + default_country?: number + phone_format?: string + date_format?: string + choices?: string[] + size?: number + } + help_text?: string + list_id?: string + [key: string]: unknown +} + +// Batch Operation +export interface MailchimpBatchOperation { + id: string + status: 'pending' | 'preprocessing' | 'started' | 'finalizing' | 'finished' + total_operations?: number + finished_operations?: number + errored_operations?: number + submitted_at?: string + completed_at?: string + response_body_url?: string + [key: string]: unknown +} + +// Tag +export interface MailchimpTag { + id: number + name: string + [key: string]: unknown +} + +// Error Response +export interface MailchimpErrorResponse { + type?: string + title?: string + status?: number + detail?: string + instance?: string + errors?: Array<{ + field?: string + message?: string + }> +} + // Helper function to extract server prefix from API key export function extractServerPrefix(apiKey: string): string { const parts = apiKey.split('-') @@ -45,9 +410,11 @@ export function buildMailchimpUrl(apiKey: string, path: string): string { } // Helper function for consistent error handling -export function handleMailchimpError(data: any, status: number, operation: string): never { +export function handleMailchimpError(data: unknown, status: number, operation: string): never { logger.error(`Mailchimp API request failed for ${operation}`, { data, status }) - const errorMessage = data.detail || data.title || data.error || data.message || 'Unknown error' + const errorData = data as Record + const errorMessage = + errorData.detail || errorData.title || errorData.error || errorData.message || 'Unknown error' throw new Error(`Mailchimp ${operation} failed: ${errorMessage}`) } diff --git a/apps/sim/tools/mailchimp/unarchive_member.ts b/apps/sim/tools/mailchimp/unarchive_member.ts index da920942083..7f25767192a 100644 --- a/apps/sim/tools/mailchimp/unarchive_member.ts +++ b/apps/sim/tools/mailchimp/unarchive_member.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types' const logger = createLogger('MailchimpUnarchiveMember') @@ -15,7 +15,7 @@ export interface MailchimpUnarchiveMemberParams { export interface MailchimpUnarchiveMemberResponse { success: boolean output: { - member: any + member: MailchimpMember metadata: { operation: 'unarchive_member' subscriberHash: string diff --git a/apps/sim/tools/mailchimp/update_audience.ts b/apps/sim/tools/mailchimp/update_audience.ts index fe7b7a3828e..1352443c199 100644 --- a/apps/sim/tools/mailchimp/update_audience.ts +++ b/apps/sim/tools/mailchimp/update_audience.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpAudience } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpUpdateAudience') @@ -16,7 +17,7 @@ export interface MailchimpUpdateAudienceParams { export interface MailchimpUpdateAudienceResponse { success: boolean output: { - list: any + list: MailchimpAudience metadata: { operation: 'update_audience' listId: string @@ -81,7 +82,7 @@ export const mailchimpUpdateAudienceTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.audienceName) body.name = params.audienceName if (params.permissionReminder) body.permission_reminder = params.permissionReminder diff --git a/apps/sim/tools/mailchimp/update_campaign.ts b/apps/sim/tools/mailchimp/update_campaign.ts index 246c9d91fee..2040418c3bf 100644 --- a/apps/sim/tools/mailchimp/update_campaign.ts +++ b/apps/sim/tools/mailchimp/update_campaign.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpCampaign } from './types' const logger = createLogger('MailchimpUpdateCampaign') @@ -14,7 +14,7 @@ export interface MailchimpUpdateCampaignParams { export interface MailchimpUpdateCampaignResponse { success: boolean output: { - campaign: any + campaign: MailchimpCampaign metadata: { operation: 'update_campaign' campaignId: string @@ -67,7 +67,7 @@ export const mailchimpUpdateCampaignTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.campaignSettings) { try { diff --git a/apps/sim/tools/mailchimp/update_interest.ts b/apps/sim/tools/mailchimp/update_interest.ts index ba9d03016c8..5da1aaf49a8 100644 --- a/apps/sim/tools/mailchimp/update_interest.ts +++ b/apps/sim/tools/mailchimp/update_interest.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpInterest } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpUpdateInterest') @@ -15,7 +16,7 @@ export interface MailchimpUpdateInterestParams { export interface MailchimpUpdateInterestResponse { success: boolean output: { - interest: any + interest: MailchimpInterest metadata: { operation: 'update_interest' interestId: string @@ -78,7 +79,7 @@ export const mailchimpUpdateInterestTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.interestName) body.name = params.interestName diff --git a/apps/sim/tools/mailchimp/update_interest_category.ts b/apps/sim/tools/mailchimp/update_interest_category.ts index b21fa61226a..898d36dc380 100644 --- a/apps/sim/tools/mailchimp/update_interest_category.ts +++ b/apps/sim/tools/mailchimp/update_interest_category.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpInterestCategory } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpUpdateInterestCategory') @@ -14,7 +15,7 @@ export interface MailchimpUpdateInterestCategoryParams { export interface MailchimpUpdateInterestCategoryResponse { success: boolean output: { - category: any + category: MailchimpInterestCategory metadata: { operation: 'update_interest_category' interestCategoryId: string @@ -71,7 +72,7 @@ export const mailchimpUpdateInterestCategoryTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.interestCategoryTitle) body.title = params.interestCategoryTitle diff --git a/apps/sim/tools/mailchimp/update_landing_page.ts b/apps/sim/tools/mailchimp/update_landing_page.ts index 8d002653e75..a84fbd8112f 100644 --- a/apps/sim/tools/mailchimp/update_landing_page.ts +++ b/apps/sim/tools/mailchimp/update_landing_page.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpLandingPage } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpUpdateLandingPage') @@ -13,7 +14,7 @@ export interface MailchimpUpdateLandingPageParams { export interface MailchimpUpdateLandingPageResponse { success: boolean output: { - landingPage: any + landingPage: MailchimpLandingPage metadata: { operation: 'update_landing_page' pageId: string @@ -60,7 +61,7 @@ export const mailchimpUpdateLandingPageTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.landingPageTitle) body.title = params.landingPageTitle diff --git a/apps/sim/tools/mailchimp/update_member.ts b/apps/sim/tools/mailchimp/update_member.ts index 0483f8bf923..fe730b23bf8 100644 --- a/apps/sim/tools/mailchimp/update_member.ts +++ b/apps/sim/tools/mailchimp/update_member.ts @@ -1,6 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' -import { buildMailchimpUrl, handleMailchimpError } from './types' +import { buildMailchimpUrl, handleMailchimpError, type MailchimpMember } from './types' const logger = createLogger('MailchimpUpdateMember') @@ -17,7 +17,7 @@ export interface MailchimpUpdateMemberParams { export interface MailchimpUpdateMemberResponse { success: boolean output: { - member: any + member: MailchimpMember metadata: { operation: 'update_member' subscriberHash: string @@ -89,7 +89,7 @@ export const mailchimpUpdateMemberTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.emailAddress) body.email_address = params.emailAddress if (params.status) body.status = params.status diff --git a/apps/sim/tools/mailchimp/update_merge_field.ts b/apps/sim/tools/mailchimp/update_merge_field.ts index 1b25460769b..c1c266256b6 100644 --- a/apps/sim/tools/mailchimp/update_merge_field.ts +++ b/apps/sim/tools/mailchimp/update_merge_field.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpMergeField } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpUpdateMergeField') @@ -14,7 +15,7 @@ export interface MailchimpUpdateMergeFieldParams { export interface MailchimpUpdateMergeFieldResponse { success: boolean output: { - mergeField: any + mergeField: MailchimpMergeField metadata: { operation: 'update_merge_field' mergeId: string @@ -68,7 +69,7 @@ export const mailchimpUpdateMergeFieldTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.mergeName) body.name = params.mergeName diff --git a/apps/sim/tools/mailchimp/update_segment.ts b/apps/sim/tools/mailchimp/update_segment.ts index 90d237514c6..ebac0e4b9e7 100644 --- a/apps/sim/tools/mailchimp/update_segment.ts +++ b/apps/sim/tools/mailchimp/update_segment.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpSegment } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpUpdateSegment') @@ -15,7 +16,7 @@ export interface MailchimpUpdateSegmentParams { export interface MailchimpUpdateSegmentResponse { success: boolean output: { - segment: any + segment: MailchimpSegment metadata: { operation: 'update_segment' segmentId: string @@ -75,7 +76,7 @@ export const mailchimpUpdateSegmentTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.segmentName) body.name = params.segmentName diff --git a/apps/sim/tools/mailchimp/update_template.ts b/apps/sim/tools/mailchimp/update_template.ts index 3e7ea3fbbaf..115fd24c2ba 100644 --- a/apps/sim/tools/mailchimp/update_template.ts +++ b/apps/sim/tools/mailchimp/update_template.ts @@ -1,5 +1,6 @@ import { createLogger } from '@/lib/logs/console/logger' import type { ToolConfig } from '@/tools/types' +import type { MailchimpTemplate } from './types' import { buildMailchimpUrl, handleMailchimpError } from './types' const logger = createLogger('MailchimpUpdateTemplate') @@ -14,7 +15,7 @@ export interface MailchimpUpdateTemplateParams { export interface MailchimpUpdateTemplateResponse { success: boolean output: { - template: any + template: MailchimpTemplate metadata: { operation: 'update_template' templateId: string @@ -67,7 +68,7 @@ export const mailchimpUpdateTemplateTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = {} + const body: Record = {} if (params.templateName) body.name = params.templateName if (params.templateHtml) body.html = params.templateHtml diff --git a/apps/sim/tools/mailgun/types.ts b/apps/sim/tools/mailgun/types.ts index dcd495510db..75cb4e0fbcb 100644 --- a/apps/sim/tools/mailgun/types.ts +++ b/apps/sim/tools/mailgun/types.ts @@ -1,5 +1,42 @@ import type { ToolResponse } from '@/tools/types' +export interface MailgunMessageHeaders { + [key: string]: string | string[] +} + +export interface MailgunMessageItem { + timestamp: number + event: string + recipient: string + sender?: string + subject?: string + deliveryStatus?: string + [key: string]: unknown +} + +export interface MailgunDomainItem { + name: string + state: string + type: string + created_at?: string + smtp_login?: string + [key: string]: unknown +} + +export interface MailgunPaging { + first?: string + next?: string + previous?: string + last?: string +} + +export interface MailgunMailingListMember { + address: string + name?: string + subscribed: boolean + vars?: Record +} + // Send Message export interface SendMessageParams { apiKey: string @@ -42,8 +79,8 @@ export interface GetMessageResult extends ToolResponse { strippedHtml: string attachmentCount: number timestamp: number - messageHeaders: any - contentIdMap: any + messageHeaders: MailgunMessageHeaders + contentIdMap: Record } } @@ -58,8 +95,8 @@ export interface ListMessagesParams { export interface ListMessagesResult extends ToolResponse { output: { success: boolean - items: any[] - paging: any + items: MailgunMessageItem[] + paging: MailgunPaging } } @@ -124,7 +161,7 @@ export interface AddListMemberResult extends ToolResponse { address: string name: string subscribed: boolean - vars: any + vars: Record } } } @@ -138,7 +175,7 @@ export interface ListDomainsResult extends ToolResponse { output: { success: boolean totalCount: number - items: any[] + items: MailgunDomainItem[] } } diff --git a/apps/sim/tools/sendgrid/add_contact.ts b/apps/sim/tools/sendgrid/add_contact.ts index 9bde449ce17..5c91e37f35f 100644 --- a/apps/sim/tools/sendgrid/add_contact.ts +++ b/apps/sim/tools/sendgrid/add_contact.ts @@ -1,4 +1,9 @@ -import type { AddContactParams, ContactResult } from '@/tools/sendgrid/types' +import type { + AddContactParams, + ContactResult, + SendGridContactObject, + SendGridContactRequest, +} from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridAddContactTool: ToolConfig = { @@ -55,7 +60,7 @@ export const sendGridAddContactTool: ToolConfig 'Content-Type': 'application/json', }), body: (params) => { - const contact: any = { + const contact: SendGridContactObject = { email: params.email, } @@ -70,7 +75,7 @@ export const sendGridAddContactTool: ToolConfig Object.assign(contact, customFields) } - const body: any = { + const body: SendGridContactRequest = { contacts: [contact], } diff --git a/apps/sim/tools/sendgrid/add_contacts_to_list.ts b/apps/sim/tools/sendgrid/add_contacts_to_list.ts index cdd943d361b..28b4105ce24 100644 --- a/apps/sim/tools/sendgrid/add_contacts_to_list.ts +++ b/apps/sim/tools/sendgrid/add_contacts_to_list.ts @@ -1,4 +1,4 @@ -import type { AddContactsToListParams } from '@/tools/sendgrid/types' +import type { AddContactsToListParams, SendGridContactObject } from '@/tools/sendgrid/types' import type { ToolConfig, ToolResponse } from '@/tools/types' export const sendGridAddContactsToListTool: ToolConfig = { @@ -38,7 +38,7 @@ export const sendGridAddContactsToListTool: ToolConfig { - const contactsArray = + const contactsArray: SendGridContactObject[] = typeof params.contacts === 'string' ? JSON.parse(params.contacts) : params.contacts return { @@ -52,11 +52,11 @@ export const sendGridAddContactsToListTool: ToolConfig => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to add contacts to list') } - const data = await response.json() + const data = (await response.json()) as { job_id: string } return { success: true, diff --git a/apps/sim/tools/sendgrid/create_list.ts b/apps/sim/tools/sendgrid/create_list.ts index e3e1b3d0f52..5bb8d7600a8 100644 --- a/apps/sim/tools/sendgrid/create_list.ts +++ b/apps/sim/tools/sendgrid/create_list.ts @@ -1,4 +1,4 @@ -import type { CreateListParams, ListResult } from '@/tools/sendgrid/types' +import type { CreateListParams, ListResult, SendGridList } from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridCreateListTool: ToolConfig = { @@ -40,11 +40,11 @@ export const sendGridCreateListTool: ToolConfig = transformResponse: async (response): Promise => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to create list') } - const data = await response.json() + const data = (await response.json()) as SendGridList return { success: true, diff --git a/apps/sim/tools/sendgrid/create_template.ts b/apps/sim/tools/sendgrid/create_template.ts index 73380e122c3..7352c41c13a 100644 --- a/apps/sim/tools/sendgrid/create_template.ts +++ b/apps/sim/tools/sendgrid/create_template.ts @@ -1,4 +1,4 @@ -import type { CreateTemplateParams, TemplateResult } from '@/tools/sendgrid/types' +import type { CreateTemplateParams, SendGridTemplate, TemplateResult } from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridCreateTemplateTool: ToolConfig = { @@ -47,11 +47,11 @@ export const sendGridCreateTemplateTool: ToolConfig => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to create template') } - const data = await response.json() + const data = (await response.json()) as SendGridTemplate return { success: true, diff --git a/apps/sim/tools/sendgrid/create_template_version.ts b/apps/sim/tools/sendgrid/create_template_version.ts index 643b486a36f..e41f43bb584 100644 --- a/apps/sim/tools/sendgrid/create_template_version.ts +++ b/apps/sim/tools/sendgrid/create_template_version.ts @@ -1,4 +1,8 @@ -import type { CreateTemplateVersionParams, TemplateVersionResult } from '@/tools/sendgrid/types' +import type { + CreateTemplateVersionParams, + SendGridTemplateVersionRequest, + TemplateVersionResult, +} from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridCreateTemplateVersionTool: ToolConfig< @@ -63,7 +67,7 @@ export const sendGridCreateTemplateVersionTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const body: any = { + const body: SendGridTemplateVersionRequest = { name: params.name, subject: params.subject, active: params.active !== undefined ? params.active : 1, diff --git a/apps/sim/tools/sendgrid/delete_contacts.ts b/apps/sim/tools/sendgrid/delete_contacts.ts index cb0d21ccbb1..c8eb811dc64 100644 --- a/apps/sim/tools/sendgrid/delete_contacts.ts +++ b/apps/sim/tools/sendgrid/delete_contacts.ts @@ -38,11 +38,11 @@ export const sendGridDeleteContactsTool: ToolConfig => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to delete contacts') } - const data = await response.json() + const data = (await response.json()) as { job_id: string } return { success: true, diff --git a/apps/sim/tools/sendgrid/delete_list.ts b/apps/sim/tools/sendgrid/delete_list.ts index 8f029cb8ebe..2e473bc6777 100644 --- a/apps/sim/tools/sendgrid/delete_list.ts +++ b/apps/sim/tools/sendgrid/delete_list.ts @@ -32,7 +32,7 @@ export const sendGridDeleteListTool: ToolConfig transformResponse: async (response): Promise => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to delete list') } diff --git a/apps/sim/tools/sendgrid/delete_template.ts b/apps/sim/tools/sendgrid/delete_template.ts index c585450f130..1d282672be7 100644 --- a/apps/sim/tools/sendgrid/delete_template.ts +++ b/apps/sim/tools/sendgrid/delete_template.ts @@ -32,7 +32,7 @@ export const sendGridDeleteTemplateTool: ToolConfig => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to delete template') } diff --git a/apps/sim/tools/sendgrid/get_contact.ts b/apps/sim/tools/sendgrid/get_contact.ts index 3485df6eb57..f2870d854ff 100644 --- a/apps/sim/tools/sendgrid/get_contact.ts +++ b/apps/sim/tools/sendgrid/get_contact.ts @@ -1,4 +1,4 @@ -import type { ContactResult, GetContactParams } from '@/tools/sendgrid/types' +import type { ContactResult, GetContactParams, SendGridContact } from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridGetContactTool: ToolConfig = { @@ -32,11 +32,11 @@ export const sendGridGetContactTool: ToolConfig transformResponse: async (response): Promise => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to get contact') } - const data = await response.json() + const data = (await response.json()) as SendGridContact return { success: true, diff --git a/apps/sim/tools/sendgrid/get_list.ts b/apps/sim/tools/sendgrid/get_list.ts index 4cda9d65ce4..1a1c03d5ded 100644 --- a/apps/sim/tools/sendgrid/get_list.ts +++ b/apps/sim/tools/sendgrid/get_list.ts @@ -1,4 +1,4 @@ -import type { GetListParams, ListResult } from '@/tools/sendgrid/types' +import type { GetListParams, ListResult, SendGridList } from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridGetListTool: ToolConfig = { @@ -32,11 +32,11 @@ export const sendGridGetListTool: ToolConfig = { transformResponse: async (response): Promise => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to get list') } - const data = await response.json() + const data = (await response.json()) as SendGridList return { success: true, diff --git a/apps/sim/tools/sendgrid/get_template.ts b/apps/sim/tools/sendgrid/get_template.ts index 73bf6d65669..0f29270c769 100644 --- a/apps/sim/tools/sendgrid/get_template.ts +++ b/apps/sim/tools/sendgrid/get_template.ts @@ -1,4 +1,4 @@ -import type { GetTemplateParams, TemplateResult } from '@/tools/sendgrid/types' +import type { GetTemplateParams, SendGridTemplate, TemplateResult } from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridGetTemplateTool: ToolConfig = { @@ -32,11 +32,11 @@ export const sendGridGetTemplateTool: ToolConfig => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to get template') } - const data = await response.json() + const data = (await response.json()) as SendGridTemplate return { success: true, diff --git a/apps/sim/tools/sendgrid/list_all_lists.ts b/apps/sim/tools/sendgrid/list_all_lists.ts index 458091ac76d..76adb3d1002 100644 --- a/apps/sim/tools/sendgrid/list_all_lists.ts +++ b/apps/sim/tools/sendgrid/list_all_lists.ts @@ -1,4 +1,4 @@ -import type { ListAllListsParams, ListsResult } from '@/tools/sendgrid/types' +import type { ListAllListsParams, ListsResult, SendGridList } from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridListAllListsTool: ToolConfig = { @@ -38,11 +38,11 @@ export const sendGridListAllListsTool: ToolConfig => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to list all lists') } - const data = await response.json() + const data = (await response.json()) as { result?: SendGridList[] } return { success: true, diff --git a/apps/sim/tools/sendgrid/list_templates.ts b/apps/sim/tools/sendgrid/list_templates.ts index 46704478c2b..2d8b6c3bd2b 100644 --- a/apps/sim/tools/sendgrid/list_templates.ts +++ b/apps/sim/tools/sendgrid/list_templates.ts @@ -1,4 +1,4 @@ -import type { ListTemplatesParams, TemplatesResult } from '@/tools/sendgrid/types' +import type { ListTemplatesParams, SendGridTemplate, TemplatesResult } from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridListTemplatesTool: ToolConfig = { @@ -47,11 +47,14 @@ export const sendGridListTemplatesTool: ToolConfig => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to list templates') } - const data = await response.json() + const data = (await response.json()) as { + result?: SendGridTemplate[] + templates?: SendGridTemplate[] + } return { success: true, diff --git a/apps/sim/tools/sendgrid/remove_contacts_from_list.ts b/apps/sim/tools/sendgrid/remove_contacts_from_list.ts index b2251d43858..cf0d461838f 100644 --- a/apps/sim/tools/sendgrid/remove_contacts_from_list.ts +++ b/apps/sim/tools/sendgrid/remove_contacts_from_list.ts @@ -47,11 +47,11 @@ export const sendGridRemoveContactsFromListTool: ToolConfig< transformResponse: async (response): Promise => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to remove contacts from list') } - const data = await response.json() + const data = (await response.json()) as { job_id?: string } return { success: true, diff --git a/apps/sim/tools/sendgrid/search_contacts.ts b/apps/sim/tools/sendgrid/search_contacts.ts index d61b17bfdfe..0f18ddab5e8 100644 --- a/apps/sim/tools/sendgrid/search_contacts.ts +++ b/apps/sim/tools/sendgrid/search_contacts.ts @@ -1,4 +1,4 @@ -import type { ContactsResult, SearchContactsParams } from '@/tools/sendgrid/types' +import type { ContactsResult, SearchContactsParams, SendGridContact } from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridSearchContactsTool: ToolConfig = { @@ -41,11 +41,14 @@ export const sendGridSearchContactsTool: ToolConfig => { if (!response.ok) { - const error = await response.json() + const error = (await response.json()) as { errors?: Array<{ message?: string }> } throw new Error(error.errors?.[0]?.message || 'Failed to search contacts') } - const data = await response.json() + const data = (await response.json()) as { + result?: SendGridContact[] + contact_count?: number + } return { success: true, diff --git a/apps/sim/tools/sendgrid/send_mail.ts b/apps/sim/tools/sendgrid/send_mail.ts index 51119a38aac..abce5e4186e 100644 --- a/apps/sim/tools/sendgrid/send_mail.ts +++ b/apps/sim/tools/sendgrid/send_mail.ts @@ -1,4 +1,9 @@ -import type { SendMailParams, SendMailResult } from '@/tools/sendgrid/types' +import type { + SendGridMailBody, + SendGridPersonalization, + SendMailParams, + SendMailResult, +} from '@/tools/sendgrid/types' import type { ToolConfig } from '@/tools/types' export const sendGridSendMailTool: ToolConfig = { @@ -108,7 +113,7 @@ export const sendGridSendMailTool: ToolConfig = 'Content-Type': 'application/json', }), body: (params) => { - const personalizations: any = { + const personalizations: SendGridPersonalization = { to: [ { email: params.to, @@ -136,7 +141,7 @@ export const sendGridSendMailTool: ToolConfig = } } - const mailBody: any = { + const mailBody: SendGridMailBody = { personalizations: [personalizations], from: { email: params.from, diff --git a/apps/sim/tools/sendgrid/types.ts b/apps/sim/tools/sendgrid/types.ts index 4fd395adc45..a3f29288313 100644 --- a/apps/sim/tools/sendgrid/types.ts +++ b/apps/sim/tools/sendgrid/types.ts @@ -1,5 +1,83 @@ import type { ToolResponse } from '@/tools/types' +// Shared type definitions +export interface SendGridContact { + id: string + email: string + first_name?: string + last_name?: string + created_at?: string + updated_at?: string + list_ids?: string[] + custom_fields?: Record +} + +export interface SendGridList { + id: string + name: string + contact_count: number + _metadata?: { + self?: string + } +} + +export interface SendGridTemplateVersion { + id: string + template_id: string + name: string + subject: string + active: number | boolean + html_content?: string + plain_content?: string + updated_at?: string +} + +export interface SendGridTemplate { + id: string + name: string + generation: 'legacy' | 'dynamic' + created_at?: string + updated_at?: string + versions?: SendGridTemplateVersion[] +} + +export interface SendGridPersonalization { + to: Array<{ email: string; name?: string }> + cc?: Array<{ email: string }> + bcc?: Array<{ email: string }> + dynamic_template_data?: Record +} + +export interface SendGridMailBody { + personalizations: SendGridPersonalization[] + from: { email: string; name?: string } + subject?: string + template_id?: string + content?: Array<{ type: 'text/plain' | 'text/html'; value?: string }> + reply_to?: { email: string; name?: string } + attachments?: any[] +} + +export interface SendGridContactObject { + email: string + first_name?: string + last_name?: string + [key: string]: unknown +} + +export interface SendGridContactRequest { + contacts: SendGridContactObject[] + list_ids?: string[] +} + +export interface SendGridTemplateVersionRequest { + name: string + subject: string + active: number | boolean + html_content?: string + plain_content?: string +} + // Common types export interface SendGridBaseParams { apiKey: string @@ -71,14 +149,14 @@ export interface ContactResult extends ToolResponse { createdAt?: string updatedAt?: string listIds?: string[] - customFields?: any + customFields?: Record message?: string } } export interface ContactsResult extends ToolResponse { output: { - contacts: any[] + contacts: SendGridContact[] contactCount?: number } } @@ -125,7 +203,7 @@ export interface ListResult extends ToolResponse { export interface ListsResult extends ToolResponse { output: { - lists: any[] + lists: SendGridList[] } } @@ -168,13 +246,13 @@ export interface TemplateResult extends ToolResponse { name: string generation: string updatedAt?: string - versions?: any[] + versions?: SendGridTemplateVersion[] } } export interface TemplatesResult extends ToolResponse { output: { - templates: any[] + templates: SendGridTemplate[] } }