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 3c70935669..38f990a211 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 ad748928c5..3f52099d8c 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 6f83cb4500..8cd77e7569 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 0000000000..0b9c8ae65e --- /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 0000000000..4f083a7abc --- /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 db663ba3ec..2a97975ae5 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 3c70935669..38f990a211 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 f22d04cd20..be2ae91440 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 0000000000..c89d2a7814 --- /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 0000000000..cdd943d361 --- /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 0000000000..e3e1b3d0f5 --- /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 0000000000..73380e122c --- /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 0000000000..643b486a36 --- /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 0000000000..cb0d21ccbb --- /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 0000000000..8f029cb8eb --- /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 0000000000..c585450f13 --- /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 0000000000..3485df6eb5 --- /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 0000000000..4cda9d65ce --- /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 0000000000..73bf6d6566 --- /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 0000000000..0a716cc07b --- /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 0000000000..458091ac76 --- /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 0000000000..46704478c2 --- /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 0000000000..b2251d4385 --- /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 0000000000..d61b17bfdf --- /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 0000000000..7b44a4e687 --- /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 0000000000..4e187fc4bd --- /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 38f990a211..c848e36eb6 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 3f52099d8c..3ace0e3907 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 0000000000..78d5374357 --- /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 0000000000..450576219a --- /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 8cd77e7569..e8a4b7478a 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 0b9c8ae65e..6bff00fae3 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 0000000000..5c7197fc55 --- /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 0000000000..d9e02f783e --- /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 34530ab9c0..21b3892423 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 8f2ed8bea0..bee0688e43 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 1cf02a1453..3eedbf4059 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 f559033757..5e272d8e64 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 0000000000..9d9e3208d1 --- /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 0000000000..1fdeb3b629 --- /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 4f083a7abc..130ef13156 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 0000000000..d6ed236059 --- /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 2a97975ae5..e7da0a7e57 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 38f990a211..c848e36eb6 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 48982d432a..a22affc129 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 660c5051ed..66cdb0ad47 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 41145c3bf8..ed51f34433 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 0000000000..90b23daf27 --- /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 0000000000..7b457c6cab --- /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 0000000000..1ef99dbe16 --- /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 0000000000..eca93c851b --- /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 0000000000..6e33e5a95b --- /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 0000000000..6d7f5d6629 --- /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 0000000000..7b8ce4104a --- /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 0000000000..509021d8a5 --- /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 0000000000..22066e5873 --- /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 0000000000..2e9bf29035 --- /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 0000000000..4735d30aa5 --- /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 0000000000..59a269d39d --- /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 0000000000..c4e7a9735c --- /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 0000000000..dcd495510d --- /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 be2ae91440..9d993891b6 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 c89d2a7814..9bde449ce1 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 7b44a4e687..51119a38aa 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 4e187fc4bd..4fd395adc4 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 0000000000..795e624955 --- /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 0000000000..bc189d62d5 --- /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 0000000000..9792262a96 --- /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 7beee8de41..231c8e38ad 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 60219b7788..510943d58b 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 f872e35ee6..c7cc5754e4 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 d9e02f783e..49c4eb9b13 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 90b23daf27..0c766df85e 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 1ef99dbe16..ed3bd8d198 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 226ab6dceb..7a917f00ca 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 ae5a786544..a4b3daf7ac 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 c5ea3d686a..0ab705a297 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 81152a8985..8d84ec807a 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 17310ec932..bb3d827f75 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 5e49f871b1..1da6c56ff4 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 add7e5dce8..fce2b86fee 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 2845a347c4..c29c562bee 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 2fb3b147b4..f33a6e7342 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 5ed9ca3f21..c2895c8cc4 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 d069404ab2..d409c96ecb 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 4c3afb767a..c278444806 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 daf71dd4b2..c4f92bc6be 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 971d7a528d..cf9383c96a 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 9b05f9f5ea..dae107aa63 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 ea47a87133..4ae25e30db 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 4e1ccdd089..a9c478d74d 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 0e98715d86..c4ef501ef8 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 ec40e0272f..4c4ac68542 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 706b8b5ae7..3e0a33bf5e 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 852a9c912f..306baef7cc 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 d83a2acd2c..d5a1bd152a 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 bc725ab7bc..65b5404240 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 bdde022c76..8e1b1bea1d 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 6d51d0457f..e550a3e3b6 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 f3ec0cf37b..c8abd6265e 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 b8095cdd9c..7147e5d005 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 42a24a1eaa..8be49b13c2 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 850f7e23b9..f4816fe778 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 64fbaa7b3a..a7e31dcc3f 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 8e1dbca19a..796429f03d 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 ed2ba417ad..171e04bb8a 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 8a594c8fb2..4c51959730 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 31a4f79389..9e079a23e4 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 db181474c6..8be121bf43 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 737756c548..9dcf0a9d82 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 2348e68639..7fcdb9d5d8 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 da92094208..7f25767192 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 fe7b7a3828..1352443c19 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 246c9d91fe..2040418c3b 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 ba9d03016c..5da1aaf49a 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 b21fa61226..898d36dc38 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 8d002653e7..a84fbd8112 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 0483f8bf92..fe730b23bf 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 1b25460769..c1c266256b 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 90d237514c..ebac0e4b9e 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 3e7ea3fbba..115fd24c2b 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 dcd495510d..75cb4e0fbc 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 9bde449ce1..5c91e37f35 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 cdd943d361..28b4105ce2 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 e3e1b3d0f5..5bb8d7600a 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 73380e122c..7352c41c13 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 643b486a36..e41f43bb58 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 cb0d21ccbb..c8eb811dc6 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 8f029cb8eb..2e473bc677 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 c585450f13..1d282672be 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 3485df6eb5..f2870d854f 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 4cda9d65ce..1a1c03d5de 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 73bf6d6566..0f29270c76 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 458091ac76..76adb3d100 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 46704478c2..2d8b6c3bd2 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 b2251d4385..cf0d461838 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 d61b17bfdf..0f18ddab5e 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 51119a38aa..abce5e4186 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 4fd395adc4..a3f2928831 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[] } }