From d2741ee2a3fa6a2669f81b2c8d271623d686bc79 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 5 May 2026 23:13:16 -0700 Subject: [PATCH 1/7] feat(emailbison): block, tools --- apps/docs/components/icons.tsx | 11 + apps/docs/components/ui/icon-mapping.ts | 2 + .../docs/content/docs/en/tools/emailbison.mdx | 462 ++++++++++++++ apps/docs/content/docs/en/tools/meta.json | 1 + apps/docs/content/docs/en/tools/posthog.mdx | 8 +- .../integrations/data/icon-mapping.ts | 2 + .../integrations/data/integrations.json | 71 +++ apps/sim/blocks/blocks/emailbison.ts | 593 ++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 11 + .../emailbison/attach_leads_to_campaign.ts | 69 ++ .../tools/emailbison/attach_tags_to_leads.ts | 71 +++ apps/sim/tools/emailbison/create_campaign.ts | 62 ++ apps/sim/tools/emailbison/create_lead.ts | 98 +++ apps/sim/tools/emailbison/create_tag.ts | 46 ++ apps/sim/tools/emailbison/get_lead.ts | 44 ++ apps/sim/tools/emailbison/index.ts | 14 + apps/sim/tools/emailbison/list_campaigns.ts | 46 ++ apps/sim/tools/emailbison/list_leads.ts | 85 +++ apps/sim/tools/emailbison/list_replies.ts | 108 ++++ apps/sim/tools/emailbison/list_tags.ts | 42 ++ apps/sim/tools/emailbison/types.ts | 254 ++++++++ apps/sim/tools/emailbison/update_campaign.ts | 117 ++++ .../emailbison/update_campaign_status.ts | 56 ++ apps/sim/tools/emailbison/update_lead.ts | 105 ++++ apps/sim/tools/emailbison/utils.ts | 445 +++++++++++++ apps/sim/tools/registry.ts | 28 + 27 files changed, 2849 insertions(+), 4 deletions(-) create mode 100644 apps/docs/content/docs/en/tools/emailbison.mdx create mode 100644 apps/sim/blocks/blocks/emailbison.ts create mode 100644 apps/sim/tools/emailbison/attach_leads_to_campaign.ts create mode 100644 apps/sim/tools/emailbison/attach_tags_to_leads.ts create mode 100644 apps/sim/tools/emailbison/create_campaign.ts create mode 100644 apps/sim/tools/emailbison/create_lead.ts create mode 100644 apps/sim/tools/emailbison/create_tag.ts create mode 100644 apps/sim/tools/emailbison/get_lead.ts create mode 100644 apps/sim/tools/emailbison/index.ts create mode 100644 apps/sim/tools/emailbison/list_campaigns.ts create mode 100644 apps/sim/tools/emailbison/list_leads.ts create mode 100644 apps/sim/tools/emailbison/list_replies.ts create mode 100644 apps/sim/tools/emailbison/list_tags.ts create mode 100644 apps/sim/tools/emailbison/types.ts create mode 100644 apps/sim/tools/emailbison/update_campaign.ts create mode 100644 apps/sim/tools/emailbison/update_campaign_status.ts create mode 100644 apps/sim/tools/emailbison/update_lead.ts create mode 100644 apps/sim/tools/emailbison/utils.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index dae53828ccb..c4bc260742b 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -415,6 +415,17 @@ export function MailIcon(props: SVGProps) { ) } +export function EmailBisonIcon(props: SVGProps) { + return ( + + + + ) +} + export function MailServerIcon(props: SVGProps) { return ( = { dynamodb: DynamoDBIcon, elasticsearch: ElasticsearchIcon, elevenlabs: ElevenLabsIcon, + emailbison: EmailBisonIcon, enrich: EnrichSoIcon, evernote: EvernoteIcon, exa: ExaAIIcon, diff --git a/apps/docs/content/docs/en/tools/emailbison.mdx b/apps/docs/content/docs/en/tools/emailbison.mdx new file mode 100644 index 00000000000..33fada79597 --- /dev/null +++ b/apps/docs/content/docs/en/tools/emailbison.mdx @@ -0,0 +1,462 @@ +--- +title: Email Bison +description: Manage Email Bison leads, campaigns, replies, and tags +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +## Usage Instructions + +Integrate Email Bison into workflows. Create and update leads, manage campaigns, attach leads to campaigns, list replies, and organize leads with tags. + + + +## Tools + +### `emailbison_list_leads` + +Retrieves leads from Email Bison with optional search and tag filters. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `search` | string | No | Search term for filtering leads | +| `campaignStatus` | string | No | Lead campaign status filter: in_sequence, sequence_finished, sequence_stopped, never_contacted, or replied | +| `tagIds` | array | No | Tag IDs to include | +| `items` | number | No | No description | +| `excludedTagIds` | array | No | Tag IDs to exclude | +| `items` | number | No | No description | +| `withoutTags` | boolean | No | Only return leads without tags | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_get_lead` + +Retrieves a lead by Email Bison lead ID or email address. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `leadId` | string | Yes | Lead ID or email address | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_create_lead` + +Creates a single lead in Email Bison. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `firstName` | string | Yes | Lead first name | +| `lastName` | string | Yes | Lead last name | +| `email` | string | Yes | Lead email address | +| `title` | string | No | Lead job title | +| `company` | string | No | Lead company | +| `notes` | string | No | Additional notes about the lead | +| `customVariables` | array | No | Custom variables to store on the lead | +| `items` | object | No | Custom variable name | +| `properties` | string | No | Custom variable name | +| `name` | string | No | No description | +| `value` | string | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_update_lead` + +Updates an existing Email Bison lead. Fields omitted from a PUT update may be cleared by Email Bison. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `leadId` | string | Yes | Lead ID or email address | +| `firstName` | string | Yes | Lead first name | +| `lastName` | string | Yes | Lead last name | +| `email` | string | Yes | Lead email address | +| `title` | string | No | Lead job title | +| `company` | string | No | Lead company | +| `notes` | string | No | Additional notes about the lead | +| `customVariables` | array | No | Custom variables to store on the lead | +| `items` | object | No | Custom variable name | +| `properties` | string | No | Custom variable name | +| `name` | string | No | No description | +| `value` | string | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_list_campaigns` + +Retrieves Email Bison campaigns. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_create_campaign` + +Creates a new Email Bison campaign. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `name` | string | Yes | Campaign name | +| `campaignType` | string | No | Campaign type: outbound or reply_followup | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_update_campaign` + +Updates Email Bison campaign settings. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `campaignId` | number | Yes | Campaign ID | +| `name` | string | No | Campaign name | +| `maxEmailsPerDay` | number | No | Maximum emails per day | +| `maxNewLeadsPerDay` | number | No | Maximum new leads per day | +| `plainText` | boolean | No | Send plain text emails | +| `openTracking` | boolean | No | Enable open tracking | +| `reputationBuilding` | boolean | No | Enable reputation building | +| `canUnsubscribe` | boolean | No | Enable unsubscribe link | +| `includeAutoRepliesInStats` | boolean | No | Include auto replies in campaign stats | +| `sequencePrioritization` | string | No | Sequence prioritization: followups or new_leads | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_update_campaign_status` + +Pauses, resumes, or archives an Email Bison campaign. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `campaignId` | number | Yes | Campaign ID | +| `action` | string | Yes | Status action: pause, resume, or archive | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_attach_leads_to_campaign` + +Adds existing Email Bison leads to a campaign. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `campaignId` | number | Yes | Campaign ID | +| `leadIds` | array | Yes | Lead IDs to add to the campaign | +| `items` | number | No | No description | +| `allowParallelSending` | boolean | No | Force add leads already in sequence in other campaigns | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_list_replies` + +Retrieves Email Bison replies with optional status, folder, campaign, sender, lead, and tag filters. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `search` | string | No | Search term for replies | +| `status` | string | No | Reply status: interested, automated_reply, or not_automated_reply | +| `folder` | string | No | Reply folder: inbox, sent, spam, bounced, or all | +| `read` | boolean | No | Filter by read state | +| `campaignId` | number | No | Campaign ID | +| `senderEmailId` | number | No | Sender email ID | +| `leadId` | number | No | Lead ID | +| `tagIds` | array | No | Tag IDs to filter replies by | +| `items` | number | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_list_tags` + +Retrieves all Email Bison tags for the authenticated workspace. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_create_tag` + +Creates a new Email Bison tag. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `name` | string | Yes | Tag name | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_attach_tags_to_leads` + +Attaches Email Bison tags to one or more leads. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Email Bison API token | +| `tagIds` | array | Yes | Tag IDs to attach | +| `items` | number | No | No description | +| `leadIds` | array | Yes | Lead IDs to tag | +| `items` | number | No | No description | +| `skipWebhooks` | boolean | No | Skip Email Bison webhooks for this action | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 1f780cff3d2..6beab98ac26 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -46,6 +46,7 @@ "dynamodb", "elasticsearch", "elevenlabs", + "emailbison", "enrich", "evernote", "exa", diff --git a/apps/docs/content/docs/en/tools/posthog.mdx b/apps/docs/content/docs/en/tools/posthog.mdx index 61b50e82810..6b471ef60fb 100644 --- a/apps/docs/content/docs/en/tools/posthog.mdx +++ b/apps/docs/content/docs/en/tools/posthog.mdx @@ -87,7 +87,7 @@ List persons (users) in PostHog. Returns user profiles with their properties and | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | +| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | | `region` | string | No | PostHog region: us \(default\) or eu | | `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) | | `limit` | number | No | Number of persons to return \(default: 100, max: 100\) | @@ -115,7 +115,7 @@ Get detailed information about a specific person in PostHog by their ID or UUID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | +| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | | `region` | string | No | PostHog region: us \(default\) or eu | | `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) | | `personId` | string | Yes | Person ID or UUID to retrieve \(e.g., "01234567-89ab-cdef-0123-456789abcdef"\) | @@ -139,7 +139,7 @@ Delete a person from PostHog. This will remove all associated events and data. U | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | +| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | | `region` | string | No | PostHog region: us \(default\) or eu | | `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) | | `personId` | string | Yes | Person ID or UUID to delete \(e.g., "01234567-89ab-cdef-0123-456789abcdef"\) | @@ -158,7 +158,7 @@ Execute a HogQL query in PostHog. HogQL is PostHog | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | +| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | | `region` | string | No | PostHog region: us \(default\) or eu | | `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) | | `query` | string | Yes | HogQL query to execute. Example: \{"kind": "HogQLQuery", "query": "SELECT event, count\(\) FROM events WHERE timestamp > now\(\) - INTERVAL 1 DAY GROUP BY event"\} | diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index 9d3280bd825..96adf9a1b62 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -51,6 +51,7 @@ import { DynamoDBIcon, ElasticsearchIcon, ElevenLabsIcon, + EmailBisonIcon, EnrichSoIcon, EvernoteIcon, ExaAIIcon, @@ -248,6 +249,7 @@ export const blockTypeToIconMap: Record = { dynamodb: DynamoDBIcon, elasticsearch: ElasticsearchIcon, elevenlabs: ElevenLabsIcon, + emailbison: EmailBisonIcon, enrich: EnrichSoIcon, evernote: EvernoteIcon, exa: ExaAIIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 346cb98feeb..43e8e52db1c 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -3633,6 +3633,77 @@ "integrationTypes": ["ai"], "tags": ["text-to-speech"] }, + { + "type": "emailbison", + "slug": "email-bison", + "name": "Email Bison", + "description": "Manage Email Bison leads, campaigns, replies, and tags", + "longDescription": "Integrate Email Bison into workflows. Create and update leads, manage campaigns, attach leads to campaigns, list replies, and organize leads with tags.", + "bgColor": "#FB7A22", + "iconName": "EmailBisonIcon", + "docsUrl": "https://docs.sim.ai/tools/emailbison", + "operations": [ + { + "name": "List Leads", + "description": "Retrieves leads from Email Bison with optional search and tag filters." + }, + { + "name": "Get Lead", + "description": "Retrieves a lead by Email Bison lead ID or email address." + }, + { + "name": "Create Lead", + "description": "Creates a single lead in Email Bison." + }, + { + "name": "Update Lead", + "description": "Updates an existing Email Bison lead. Fields omitted from a PUT update may be cleared by Email Bison." + }, + { + "name": "List Campaigns", + "description": "Retrieves Email Bison campaigns." + }, + { + "name": "Create Campaign", + "description": "Creates a new Email Bison campaign." + }, + { + "name": "Update Campaign", + "description": "Updates Email Bison campaign settings." + }, + { + "name": "Update Campaign Status", + "description": "Pauses, resumes, or archives an Email Bison campaign." + }, + { + "name": "Attach Leads to Campaign", + "description": "Adds existing Email Bison leads to a campaign." + }, + { + "name": "List Replies", + "description": "Retrieves Email Bison replies with optional status, folder, campaign, sender, lead, and tag filters." + }, + { + "name": "List Tags", + "description": "Retrieves all Email Bison tags for the authenticated workspace." + }, + { + "name": "Create Tag", + "description": "Creates a new Email Bison tag." + }, + { + "name": "Attach Tags to Leads", + "description": "Attaches Email Bison tags to one or more leads." + } + ], + "operationCount": 13, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationTypes": ["email", "developer-tools", "sales"], + "tags": ["sales-engagement", "email-marketing", "automation"] + }, { "type": "openai", "slug": "embeddings", diff --git a/apps/sim/blocks/blocks/emailbison.ts b/apps/sim/blocks/blocks/emailbison.ts new file mode 100644 index 00000000000..48d5c0dfb18 --- /dev/null +++ b/apps/sim/blocks/blocks/emailbison.ts @@ -0,0 +1,593 @@ +import { EmailBisonIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import type { EmailBisonResponse } from '@/tools/emailbison/types' + +const LEAD_MUTATION_OPERATIONS = ['create_lead', 'update_lead'] as const +const LEAD_LIST_OPERATIONS = ['list_leads'] as const +const CAMPAIGN_ID_OPERATIONS = [ + 'update_campaign', + 'update_campaign_status', + 'attach_leads_to_campaign', +] as const +const REPLY_FILTER_OPERATIONS = ['list_replies'] as const +const TAG_FILTER_OPERATIONS = ['list_leads', 'list_replies'] as const + +export const EmailBisonBlock: BlockConfig = { + type: 'emailbison', + name: 'Email Bison', + description: 'Manage Email Bison leads, campaigns, replies, and tags', + longDescription: + 'Integrate Email Bison into workflows. Create and update leads, manage campaigns, attach leads to campaigns, list replies, and organize leads with tags.', + docsLink: 'https://docs.sim.ai/tools/emailbison', + category: 'tools', + integrationType: IntegrationType.Email, + tags: ['sales-engagement', 'email-marketing', 'automation'], + bgColor: '#FB7A22', + icon: EmailBisonIcon, + authMode: AuthMode.ApiKey, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Leads', id: 'list_leads' }, + { label: 'Get Lead', id: 'get_lead' }, + { label: 'Create Lead', id: 'create_lead' }, + { label: 'Update Lead', id: 'update_lead' }, + { label: 'List Campaigns', id: 'list_campaigns' }, + { label: 'Create Campaign', id: 'create_campaign' }, + { label: 'Update Campaign', id: 'update_campaign' }, + { label: 'Update Campaign Status', id: 'update_campaign_status' }, + { label: 'Attach Leads to Campaign', id: 'attach_leads_to_campaign' }, + { label: 'List Replies', id: 'list_replies' }, + { label: 'List Tags', id: 'list_tags' }, + { label: 'Create Tag', id: 'create_tag' }, + { label: 'Attach Tags to Leads', id: 'attach_tags_to_leads' }, + ], + value: () => 'list_leads', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + password: true, + placeholder: 'Enter your Email Bison API token', + required: true, + }, + { + id: 'leadId', + title: 'Lead ID or Email', + type: 'short-input', + placeholder: 'Lead ID or email address', + required: { field: 'operation', value: ['get_lead', 'update_lead'] }, + condition: { field: 'operation', value: ['get_lead', 'update_lead'] }, + }, + { + id: 'search', + title: 'Search', + type: 'short-input', + placeholder: 'Search term', + condition: { + field: 'operation', + value: [...LEAD_LIST_OPERATIONS, ...REPLY_FILTER_OPERATIONS], + }, + }, + { + id: 'campaignStatus', + title: 'Lead Campaign Status', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'In Sequence', id: 'in_sequence' }, + { label: 'Sequence Finished', id: 'sequence_finished' }, + { label: 'Sequence Stopped', id: 'sequence_stopped' }, + { label: 'Never Contacted', id: 'never_contacted' }, + { label: 'Replied', id: 'replied' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_leads' }, + mode: 'advanced', + }, + { + id: 'firstName', + title: 'First Name', + type: 'short-input', + placeholder: 'John', + required: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + condition: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + }, + { + id: 'lastName', + title: 'Last Name', + type: 'short-input', + placeholder: 'Doe', + required: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + condition: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + }, + { + id: 'email', + title: 'Email', + type: 'short-input', + placeholder: 'john@example.com', + required: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + condition: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + }, + { + id: 'title', + title: 'Title', + type: 'short-input', + placeholder: 'Engineer', + condition: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + mode: 'advanced', + }, + { + id: 'company', + title: 'Company', + type: 'short-input', + placeholder: 'Acme Inc.', + condition: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + mode: 'advanced', + }, + { + id: 'notes', + title: 'Notes', + type: 'long-input', + placeholder: 'Additional notes', + condition: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + mode: 'advanced', + }, + { + id: 'customVariables', + title: 'Custom Variables', + type: 'long-input', + placeholder: '[{"name":"linkedin_url","value":"https://linkedin.com/in/john"}]', + condition: { field: 'operation', value: [...LEAD_MUTATION_OPERATIONS] }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of Email Bison custom variables. Each item must have name and value. Return ONLY the JSON array.', + generationType: 'json-object', + }, + mode: 'advanced', + }, + { + id: 'campaignId', + title: 'Campaign ID', + type: 'short-input', + placeholder: '123', + required: { field: 'operation', value: [...CAMPAIGN_ID_OPERATIONS] }, + condition: { + field: 'operation', + value: [...CAMPAIGN_ID_OPERATIONS, ...REPLY_FILTER_OPERATIONS], + }, + }, + { + id: 'campaignName', + title: 'Campaign Name', + type: 'short-input', + placeholder: 'Outbound Campaign', + required: { field: 'operation', value: 'create_campaign' }, + condition: { field: 'operation', value: ['create_campaign', 'update_campaign'] }, + }, + { + id: 'campaignType', + title: 'Campaign Type', + type: 'dropdown', + options: [ + { label: 'Outbound', id: 'outbound' }, + { label: 'Reply Follow-up', id: 'reply_followup' }, + ], + value: () => 'outbound', + condition: { field: 'operation', value: 'create_campaign' }, + mode: 'advanced', + }, + { + id: 'action', + title: 'Status Action', + type: 'dropdown', + options: [ + { label: 'Pause', id: 'pause' }, + { label: 'Resume', id: 'resume' }, + { label: 'Archive', id: 'archive' }, + ], + value: () => 'pause', + required: { field: 'operation', value: 'update_campaign_status' }, + condition: { field: 'operation', value: 'update_campaign_status' }, + }, + { + id: 'maxEmailsPerDay', + title: 'Max Emails Per Day', + type: 'short-input', + placeholder: '500', + condition: { field: 'operation', value: 'update_campaign' }, + mode: 'advanced', + }, + { + id: 'maxNewLeadsPerDay', + title: 'Max New Leads Per Day', + type: 'short-input', + placeholder: '100', + condition: { field: 'operation', value: 'update_campaign' }, + mode: 'advanced', + }, + { + id: 'sequencePrioritization', + title: 'Sequence Prioritization', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'Follow-ups', id: 'followups' }, + { label: 'New Leads', id: 'new_leads' }, + ], + value: () => '', + condition: { field: 'operation', value: 'update_campaign' }, + mode: 'advanced', + }, + { + id: 'plainText', + title: 'Plain Text', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'update_campaign' }, + mode: 'advanced', + }, + { + id: 'openTracking', + title: 'Open Tracking', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'update_campaign' }, + mode: 'advanced', + }, + { + id: 'reputationBuilding', + title: 'Reputation Building', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'update_campaign' }, + mode: 'advanced', + }, + { + id: 'canUnsubscribe', + title: 'Can Unsubscribe', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'update_campaign' }, + mode: 'advanced', + }, + { + id: 'includeAutoRepliesInStats', + title: 'Include Auto Replies in Stats', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: '' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'update_campaign' }, + mode: 'advanced', + }, + { + id: 'leadIds', + title: 'Lead IDs', + type: 'short-input', + placeholder: '1,2,3', + required: { field: 'operation', value: ['attach_leads_to_campaign', 'attach_tags_to_leads'] }, + condition: { + field: 'operation', + value: ['attach_leads_to_campaign', 'attach_tags_to_leads'], + }, + }, + { + id: 'allowParallelSending', + title: 'Allow Parallel Sending', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + value: () => 'false', + condition: { field: 'operation', value: 'attach_leads_to_campaign' }, + mode: 'advanced', + }, + { + id: 'replyStatus', + title: 'Reply Status', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Interested', id: 'interested' }, + { label: 'Automated Reply', id: 'automated_reply' }, + { label: 'Not Automated Reply', id: 'not_automated_reply' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_replies' }, + mode: 'advanced', + }, + { + id: 'folder', + title: 'Folder', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Inbox', id: 'inbox' }, + { label: 'Sent', id: 'sent' }, + { label: 'Spam', id: 'spam' }, + { label: 'Bounced', id: 'bounced' }, + { label: 'All', id: 'all' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_replies' }, + mode: 'advanced', + }, + { + id: 'read', + title: 'Read', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Read', id: 'true' }, + { label: 'Unread', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_replies' }, + mode: 'advanced', + }, + { + id: 'senderEmailId', + title: 'Sender Email ID', + type: 'short-input', + placeholder: '243', + condition: { field: 'operation', value: 'list_replies' }, + mode: 'advanced', + }, + { + id: 'replyLeadId', + title: 'Lead ID', + type: 'short-input', + placeholder: '14', + condition: { field: 'operation', value: 'list_replies' }, + mode: 'advanced', + }, + { + id: 'tagName', + title: 'Tag Name', + type: 'short-input', + placeholder: 'Interested', + required: { field: 'operation', value: 'create_tag' }, + condition: { field: 'operation', value: 'create_tag' }, + }, + { + id: 'tagIds', + title: 'Tag IDs', + type: 'short-input', + placeholder: '1,2,3', + required: { field: 'operation', value: 'attach_tags_to_leads' }, + condition: { field: 'operation', value: 'attach_tags_to_leads' }, + }, + { + id: 'filterTagIds', + title: 'Filter Tag IDs', + type: 'short-input', + placeholder: '1,2,3', + condition: { field: 'operation', value: [...TAG_FILTER_OPERATIONS] }, + mode: 'advanced', + }, + { + id: 'excludedTagIds', + title: 'Excluded Tag IDs', + type: 'short-input', + placeholder: '4,5,6', + condition: { field: 'operation', value: 'list_leads' }, + mode: 'advanced', + }, + { + id: 'withoutTags', + title: 'Without Tags', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + value: () => 'false', + condition: { field: 'operation', value: 'list_leads' }, + mode: 'advanced', + }, + { + id: 'skipWebhooks', + title: 'Skip Webhooks', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + value: () => 'false', + condition: { field: 'operation', value: 'attach_tags_to_leads' }, + mode: 'advanced', + }, + ], + tools: { + access: [ + 'emailbison_list_leads', + 'emailbison_get_lead', + 'emailbison_create_lead', + 'emailbison_update_lead', + 'emailbison_list_campaigns', + 'emailbison_create_campaign', + 'emailbison_update_campaign', + 'emailbison_update_campaign_status', + 'emailbison_attach_leads_to_campaign', + 'emailbison_list_replies', + 'emailbison_list_tags', + 'emailbison_create_tag', + 'emailbison_attach_tags_to_leads', + ], + config: { + tool: (params) => `emailbison_${params.operation}`, + params: (params) => ({ + leadId: + params.operation === 'list_replies' ? toNumberParam(params.replyLeadId) : params.leadId, + leadIds: parseNumberList(params.leadIds), + tagIds: parseNumberList( + params.operation === 'attach_tags_to_leads' ? params.tagIds : params.filterTagIds + ), + excludedTagIds: parseNumberList(params.excludedTagIds), + customVariables: parseJsonArray(params.customVariables), + campaignId: toNumberParam(params.campaignId), + campaignType: emptyToUndefined(params.campaignType), + name: + params.operation === 'create_tag' || params.operation === 'create_campaign' + ? params.operation === 'create_tag' + ? params.tagName + : params.campaignName + : emptyToUndefined(params.campaignName), + action: params.action, + maxEmailsPerDay: toNumberParam(params.maxEmailsPerDay), + maxNewLeadsPerDay: toNumberParam(params.maxNewLeadsPerDay), + sequencePrioritization: emptyToUndefined(params.sequencePrioritization), + plainText: toBooleanParam(params.plainText), + openTracking: toBooleanParam(params.openTracking), + reputationBuilding: toBooleanParam(params.reputationBuilding), + canUnsubscribe: toBooleanParam(params.canUnsubscribe), + includeAutoRepliesInStats: toBooleanParam(params.includeAutoRepliesInStats), + allowParallelSending: toBooleanParam(params.allowParallelSending), + skipWebhooks: toBooleanParam(params.skipWebhooks), + withoutTags: toBooleanParam(params.withoutTags), + read: toBooleanParam(params.read), + senderEmailId: toNumberParam(params.senderEmailId), + campaignStatus: emptyToUndefined(params.campaignStatus), + status: + params.operation === 'list_replies' ? emptyToUndefined(params.replyStatus) : undefined, + }), + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Email Bison API token' }, + leadId: { type: 'string', description: 'Lead ID or email address' }, + search: { type: 'string', description: 'Search term' }, + campaignStatus: { type: 'string', description: 'Lead campaign status filter' }, + firstName: { type: 'string', description: 'Lead first name' }, + lastName: { type: 'string', description: 'Lead last name' }, + email: { type: 'string', description: 'Lead email address' }, + title: { type: 'string', description: 'Lead title' }, + company: { type: 'string', description: 'Lead company' }, + notes: { type: 'string', description: 'Lead notes' }, + customVariables: { type: 'array', description: 'Lead custom variables' }, + campaignId: { type: 'number', description: 'Campaign ID' }, + campaignName: { type: 'string', description: 'Campaign name' }, + campaignType: { type: 'string', description: 'Campaign type' }, + action: { type: 'string', description: 'Campaign status action' }, + maxEmailsPerDay: { type: 'number', description: 'Maximum emails per day' }, + maxNewLeadsPerDay: { type: 'number', description: 'Maximum new leads per day' }, + sequencePrioritization: { type: 'string', description: 'Campaign sequence prioritization' }, + plainText: { type: 'boolean', description: 'Whether campaign emails should be plain text' }, + openTracking: { type: 'boolean', description: 'Whether open tracking should be enabled' }, + reputationBuilding: { type: 'boolean', description: 'Whether reputation building is enabled' }, + canUnsubscribe: { type: 'boolean', description: 'Whether recipients can unsubscribe' }, + includeAutoRepliesInStats: { + type: 'boolean', + description: 'Whether auto replies are included in campaign stats', + }, + leadIds: { type: 'array', description: 'Lead IDs' }, + allowParallelSending: { type: 'boolean', description: 'Allow parallel sending' }, + replyStatus: { type: 'string', description: 'Reply status filter' }, + folder: { type: 'string', description: 'Reply folder filter' }, + read: { type: 'boolean', description: 'Reply read filter' }, + senderEmailId: { type: 'number', description: 'Sender email ID' }, + replyLeadId: { type: 'number', description: 'Reply lead ID filter' }, + tagName: { type: 'string', description: 'Tag name' }, + tagIds: { type: 'array', description: 'Tag IDs' }, + filterTagIds: { type: 'array', description: 'Tag IDs to filter by' }, + excludedTagIds: { type: 'array', description: 'Excluded tag IDs' }, + withoutTags: { type: 'boolean', description: 'Only include leads without tags' }, + skipWebhooks: { type: 'boolean', description: 'Skip Email Bison webhooks' }, + }, + outputs: { + leads: { type: 'array', description: 'List of leads' }, + campaigns: { type: 'array', description: 'List of campaigns' }, + replies: { type: 'array', description: 'List of replies' }, + tags: { type: 'array', description: 'List of tags' }, + count: { type: 'number', description: 'Number of returned records' }, + id: { type: 'number', description: 'Record ID' }, + uuid: { type: 'string', description: 'Record UUID' }, + name: { type: 'string', description: 'Campaign or tag name' }, + first_name: { type: 'string', description: 'Lead first name' }, + last_name: { type: 'string', description: 'Lead last name' }, + email: { type: 'string', description: 'Lead email address' }, + status: { type: 'string', description: 'Record status' }, + success: { type: 'boolean', description: 'Whether the action succeeded' }, + message: { type: 'string', description: 'Action message' }, + }, +} + +function parseNumberList(value: unknown): number[] | undefined { + if (Array.isArray(value)) { + const numbers = value.map(toNumberParam).filter((number) => number !== undefined) + return numbers.length > 0 ? numbers : undefined + } + + if (typeof value !== 'string') return undefined + + const numbers = value + .split(/[\s,]+/) + .map(toNumberParam) + .filter((number) => number !== undefined) + + return numbers.length > 0 ? numbers : undefined +} + +function parseJsonArray(value: unknown): unknown[] | undefined { + if (Array.isArray(value)) return value + if (typeof value !== 'string' || value.trim() === '') return undefined + + try { + const parsed: unknown = JSON.parse(value) + return Array.isArray(parsed) ? parsed : undefined + } catch { + return undefined + } +} + +function toNumberParam(value: unknown): number | undefined { + if (typeof value === 'number') return Number.isFinite(value) ? value : undefined + if (typeof value !== 'string' || value.trim() === '') return undefined + + const parsed = Number(value) + return Number.isFinite(parsed) ? parsed : undefined +} + +function toBooleanParam(value: unknown): boolean | undefined { + if (typeof value === 'boolean') return value + if (typeof value !== 'string' || value.trim() === '') return undefined + if (value === 'true') return true + if (value === 'false') return false + return undefined +} + +function emptyToUndefined(value: unknown): unknown { + return value === '' ? undefined : value +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index aacf6d49431..5a8e162fe6f 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -48,6 +48,7 @@ import { DuckDuckGoBlock } from '@/blocks/blocks/duckduckgo' import { DynamoDBBlock } from '@/blocks/blocks/dynamodb' import { ElasticsearchBlock } from '@/blocks/blocks/elasticsearch' import { ElevenLabsBlock } from '@/blocks/blocks/elevenlabs' +import { EmailBisonBlock } from '@/blocks/blocks/emailbison' import { EnrichBlock } from '@/blocks/blocks/enrich' import { EvaluatorBlock } from '@/blocks/blocks/evaluator' import { EvernoteBlock } from '@/blocks/blocks/evernote' @@ -283,6 +284,7 @@ export const registry: Record = { dub: DubBlock, duckduckgo: DuckDuckGoBlock, dynamodb: DynamoDBBlock, + emailbison: EmailBisonBlock, elasticsearch: ElasticsearchBlock, elevenlabs: ElevenLabsBlock, fathom: FathomBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index dae53828ccb..c4bc260742b 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -415,6 +415,17 @@ export function MailIcon(props: SVGProps) { ) } +export function EmailBisonIcon(props: SVGProps) { + return ( + + + + ) +} + export function MailServerIcon(props: SVGProps) { return ( = { + id: 'emailbison_attach_leads_to_campaign', + name: 'Email Bison Attach Leads to Campaign', + description: 'Adds existing Email Bison leads to a campaign.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + campaignId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Campaign ID', + }, + leadIds: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Lead IDs to add to the campaign', + items: { type: 'number', description: 'Lead ID' }, + }, + allowParallelSending: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Force add leads already in sequence in other campaigns', + }, + }, + request: { + url: (params) => emailBisonUrl(`/api/campaigns/${params.campaignId}/leads/attach-leads`), + method: 'POST', + headers: emailBisonHeaders, + body: (params) => + jsonBody({ + lead_ids: params.leadIds, + allow_parallel_sending: params.allowParallelSending, + }), + }, + transformResponse: async (response) => { + const data = await emailBisonData(response) + + return { + success: true, + output: actionOutput(data), + } + }, + outputs: actionOutputs, +} diff --git a/apps/sim/tools/emailbison/attach_tags_to_leads.ts b/apps/sim/tools/emailbison/attach_tags_to_leads.ts new file mode 100644 index 00000000000..93a6fd793da --- /dev/null +++ b/apps/sim/tools/emailbison/attach_tags_to_leads.ts @@ -0,0 +1,71 @@ +import type { + EmailBisonActionResponse, + EmailBisonAttachTagsToLeadsParams, +} from '@/tools/emailbison/types' +import { + actionOutput, + actionOutputs, + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + jsonBody, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const attachTagsToLeadsTool: ToolConfig< + EmailBisonAttachTagsToLeadsParams, + EmailBisonActionResponse +> = { + id: 'emailbison_attach_tags_to_leads', + name: 'Email Bison Attach Tags to Leads', + description: 'Attaches Email Bison tags to one or more leads.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + tagIds: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Tag IDs to attach', + items: { type: 'number', description: 'Tag ID' }, + }, + leadIds: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Lead IDs to tag', + items: { type: 'number', description: 'Lead ID' }, + }, + skipWebhooks: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Skip Email Bison webhooks for this action', + }, + }, + request: { + url: () => emailBisonUrl('/api/tags/attach-to-leads'), + method: 'POST', + headers: emailBisonHeaders, + body: (params) => + jsonBody({ + tag_ids: params.tagIds, + lead_ids: params.leadIds, + skip_webhooks: params.skipWebhooks, + }), + }, + transformResponse: async (response) => { + const data = await emailBisonData(response) + + return { + success: true, + output: actionOutput(data), + } + }, + outputs: actionOutputs, +} diff --git a/apps/sim/tools/emailbison/create_campaign.ts b/apps/sim/tools/emailbison/create_campaign.ts new file mode 100644 index 00000000000..a2971eaa03b --- /dev/null +++ b/apps/sim/tools/emailbison/create_campaign.ts @@ -0,0 +1,62 @@ +import type { + EmailBisonCampaignResponse, + EmailBisonCreateCampaignParams, +} from '@/tools/emailbison/types' +import { + campaignOutputs, + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + jsonBody, + mapCampaign, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const createCampaignTool: ToolConfig< + EmailBisonCreateCampaignParams, + EmailBisonCampaignResponse +> = { + id: 'emailbison_create_campaign', + name: 'Email Bison Create Campaign', + description: 'Creates a new Email Bison campaign.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Campaign name', + }, + campaignType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Campaign type: outbound or reply_followup', + }, + }, + request: { + url: () => emailBisonUrl('/api/campaigns'), + method: 'POST', + headers: emailBisonHeaders, + body: (params) => + jsonBody({ + name: params.name, + type: params.campaignType, + }), + }, + transformResponse: async (response) => { + const data = await emailBisonData(response) + + return { + success: true, + output: mapCampaign(data), + } + }, + outputs: campaignOutputs, +} diff --git a/apps/sim/tools/emailbison/create_lead.ts b/apps/sim/tools/emailbison/create_lead.ts new file mode 100644 index 00000000000..267a9de0af8 --- /dev/null +++ b/apps/sim/tools/emailbison/create_lead.ts @@ -0,0 +1,98 @@ +import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types' +import { + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + jsonBody, + leadOutputs, + mapLead, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const createLeadTool: ToolConfig = { + id: 'emailbison_create_lead', + name: 'Email Bison Create Lead', + description: 'Creates a single lead in Email Bison.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + firstName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Lead first name', + }, + lastName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Lead last name', + }, + email: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Lead email address', + }, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Lead job title', + }, + company: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Lead company', + }, + notes: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Additional notes about the lead', + }, + customVariables: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Custom variables to store on the lead', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Custom variable name' }, + value: { type: 'string', description: 'Custom variable value' }, + }, + }, + }, + }, + request: { + url: () => emailBisonUrl('/api/leads'), + method: 'POST', + headers: emailBisonHeaders, + body: (params) => + jsonBody({ + first_name: params.firstName, + last_name: params.lastName, + email: params.email, + title: params.title, + company: params.company, + notes: params.notes, + custom_variables: params.customVariables, + }), + }, + transformResponse: async (response) => { + const data = await emailBisonData(response) + + return { + success: true, + output: mapLead(data), + } + }, + outputs: leadOutputs, +} diff --git a/apps/sim/tools/emailbison/create_tag.ts b/apps/sim/tools/emailbison/create_tag.ts new file mode 100644 index 00000000000..2c12e1788c9 --- /dev/null +++ b/apps/sim/tools/emailbison/create_tag.ts @@ -0,0 +1,46 @@ +import type { EmailBisonCreateTagParams, EmailBisonTagResponse } from '@/tools/emailbison/types' +import { + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + jsonBody, + mapTag, + tagOutputs, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const createTagTool: ToolConfig = { + id: 'emailbison_create_tag', + name: 'Email Bison Create Tag', + description: 'Creates a new Email Bison tag.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Tag name', + }, + }, + request: { + url: () => emailBisonUrl('/api/tags'), + method: 'POST', + headers: emailBisonHeaders, + body: (params) => jsonBody({ name: params.name }), + }, + transformResponse: async (response) => { + const data = await emailBisonData(response) + + return { + success: true, + output: mapTag(data), + } + }, + outputs: tagOutputs, +} diff --git a/apps/sim/tools/emailbison/get_lead.ts b/apps/sim/tools/emailbison/get_lead.ts new file mode 100644 index 00000000000..fc5d51ed67d --- /dev/null +++ b/apps/sim/tools/emailbison/get_lead.ts @@ -0,0 +1,44 @@ +import type { EmailBisonGetLeadParams, EmailBisonLeadResponse } from '@/tools/emailbison/types' +import { + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + leadOutputs, + mapLead, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const getLeadTool: ToolConfig = { + id: 'emailbison_get_lead', + name: 'Email Bison Get Lead', + description: 'Retrieves a lead by Email Bison lead ID or email address.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + leadId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Lead ID or email address', + }, + }, + request: { + url: (params) => emailBisonUrl(`/api/leads/${encodeURIComponent(params.leadId.trim())}`), + method: 'GET', + headers: emailBisonHeaders, + }, + transformResponse: async (response) => { + const data = await emailBisonData(response) + + return { + success: true, + output: mapLead(data), + } + }, + outputs: leadOutputs, +} diff --git a/apps/sim/tools/emailbison/index.ts b/apps/sim/tools/emailbison/index.ts new file mode 100644 index 00000000000..ecbcfb6aa2a --- /dev/null +++ b/apps/sim/tools/emailbison/index.ts @@ -0,0 +1,14 @@ +export { attachLeadsToCampaignTool as emailBisonAttachLeadsToCampaignTool } from '@/tools/emailbison/attach_leads_to_campaign' +export { attachTagsToLeadsTool as emailBisonAttachTagsToLeadsTool } from '@/tools/emailbison/attach_tags_to_leads' +export { createCampaignTool as emailBisonCreateCampaignTool } from '@/tools/emailbison/create_campaign' +export { createLeadTool as emailBisonCreateLeadTool } from '@/tools/emailbison/create_lead' +export { createTagTool as emailBisonCreateTagTool } from '@/tools/emailbison/create_tag' +export { getLeadTool as emailBisonGetLeadTool } from '@/tools/emailbison/get_lead' +export { listCampaignsTool as emailBisonListCampaignsTool } from '@/tools/emailbison/list_campaigns' +export { listLeadsTool as emailBisonListLeadsTool } from '@/tools/emailbison/list_leads' +export { listRepliesTool as emailBisonListRepliesTool } from '@/tools/emailbison/list_replies' +export { listTagsTool as emailBisonListTagsTool } from '@/tools/emailbison/list_tags' +export * from '@/tools/emailbison/types' +export { updateCampaignTool as emailBisonUpdateCampaignTool } from '@/tools/emailbison/update_campaign' +export { updateCampaignStatusTool as emailBisonUpdateCampaignStatusTool } from '@/tools/emailbison/update_campaign_status' +export { updateLeadTool as emailBisonUpdateLeadTool } from '@/tools/emailbison/update_lead' diff --git a/apps/sim/tools/emailbison/list_campaigns.ts b/apps/sim/tools/emailbison/list_campaigns.ts new file mode 100644 index 00000000000..5014e84cd09 --- /dev/null +++ b/apps/sim/tools/emailbison/list_campaigns.ts @@ -0,0 +1,46 @@ +import type { + EmailBisonBaseParams, + EmailBisonListCampaignsResponse, +} from '@/tools/emailbison/types' +import { + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + listCampaignsOutputs, + mapCampaign, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const listCampaignsTool: ToolConfig = + { + id: 'emailbison_list_campaigns', + name: 'Email Bison List Campaigns', + description: 'Retrieves Email Bison campaigns.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + }, + request: { + url: () => emailBisonUrl('/api/campaigns'), + method: 'GET', + headers: emailBisonHeaders, + }, + transformResponse: async (response) => { + const data = (await emailBisonData(response)) ?? [] + const campaigns = Array.isArray(data) ? data.map(mapCampaign) : [] + + return { + success: true, + output: { + campaigns, + count: campaigns.length, + }, + } + }, + outputs: listCampaignsOutputs, + } diff --git a/apps/sim/tools/emailbison/list_leads.ts b/apps/sim/tools/emailbison/list_leads.ts new file mode 100644 index 00000000000..8d9368a1946 --- /dev/null +++ b/apps/sim/tools/emailbison/list_leads.ts @@ -0,0 +1,85 @@ +import type { + EmailBisonListLeadsParams, + EmailBisonListLeadsResponse, +} from '@/tools/emailbison/types' +import { + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + listLeadsOutputs, + mapLead, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const listLeadsTool: ToolConfig = { + id: 'emailbison_list_leads', + name: 'Email Bison List Leads', + description: 'Retrieves leads from Email Bison with optional search and tag filters.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term for filtering leads', + }, + campaignStatus: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Lead campaign status filter: in_sequence, sequence_finished, sequence_stopped, never_contacted, or replied', + }, + tagIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Tag IDs to include', + items: { type: 'number', description: 'Tag ID' }, + }, + excludedTagIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Tag IDs to exclude', + items: { type: 'number', description: 'Tag ID' }, + }, + withoutTags: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Only return leads without tags', + }, + }, + request: { + url: (params) => + emailBisonUrl('/api/leads', { + search: params.search, + 'filters.lead_campaign_status': params.campaignStatus, + 'filters.tag_ids': params.tagIds, + 'filters.excluded_tag_ids': params.excludedTagIds, + 'filters.without_tags': params.withoutTags, + }), + method: 'GET', + headers: emailBisonHeaders, + }, + transformResponse: async (response) => { + const data = (await emailBisonData(response)) ?? [] + const leads = Array.isArray(data) ? data.map(mapLead) : [] + + return { + success: true, + output: { + leads, + count: leads.length, + }, + } + }, + outputs: listLeadsOutputs, +} diff --git a/apps/sim/tools/emailbison/list_replies.ts b/apps/sim/tools/emailbison/list_replies.ts new file mode 100644 index 00000000000..00bbd913602 --- /dev/null +++ b/apps/sim/tools/emailbison/list_replies.ts @@ -0,0 +1,108 @@ +import type { + EmailBisonListRepliesParams, + EmailBisonListRepliesResponse, +} from '@/tools/emailbison/types' +import { + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + listRepliesOutputs, + mapReply, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const listRepliesTool: ToolConfig< + EmailBisonListRepliesParams, + EmailBisonListRepliesResponse +> = { + id: 'emailbison_list_replies', + name: 'Email Bison List Replies', + description: + 'Retrieves Email Bison replies with optional status, folder, campaign, sender, lead, and tag filters.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term for replies', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Reply status: interested, automated_reply, or not_automated_reply', + }, + folder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Reply folder: inbox, sent, spam, bounced, or all', + }, + read: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Filter by read state', + }, + campaignId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Campaign ID', + }, + senderEmailId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Sender email ID', + }, + leadId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Lead ID', + }, + tagIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Tag IDs to filter replies by', + items: { type: 'number', description: 'Tag ID' }, + }, + }, + request: { + url: (params) => + emailBisonUrl('/api/replies', { + search: params.search, + status: params.status, + folder: params.folder, + read: params.read, + campaign_id: params.campaignId, + sender_email_id: params.senderEmailId, + lead_id: params.leadId, + tag_ids: params.tagIds, + }), + method: 'GET', + headers: emailBisonHeaders, + }, + transformResponse: async (response) => { + const data = (await emailBisonData(response)) ?? [] + const replies = Array.isArray(data) ? data.map(mapReply) : [] + + return { + success: true, + output: { + replies, + count: replies.length, + }, + } + }, + outputs: listRepliesOutputs, +} diff --git a/apps/sim/tools/emailbison/list_tags.ts b/apps/sim/tools/emailbison/list_tags.ts new file mode 100644 index 00000000000..680c58093a1 --- /dev/null +++ b/apps/sim/tools/emailbison/list_tags.ts @@ -0,0 +1,42 @@ +import type { EmailBisonBaseParams, EmailBisonListTagsResponse } from '@/tools/emailbison/types' +import { + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + listTagsOutputs, + mapTag, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const listTagsTool: ToolConfig = { + id: 'emailbison_list_tags', + name: 'Email Bison List Tags', + description: 'Retrieves all Email Bison tags for the authenticated workspace.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + }, + request: { + url: () => emailBisonUrl('/api/tags'), + method: 'GET', + headers: emailBisonHeaders, + }, + transformResponse: async (response) => { + const data = (await emailBisonData(response)) ?? [] + const tags = Array.isArray(data) ? data.map(mapTag) : [] + + return { + success: true, + output: { + tags, + count: tags.length, + }, + } + }, + outputs: listTagsOutputs, +} diff --git a/apps/sim/tools/emailbison/types.ts b/apps/sim/tools/emailbison/types.ts new file mode 100644 index 00000000000..f66782fe807 --- /dev/null +++ b/apps/sim/tools/emailbison/types.ts @@ -0,0 +1,254 @@ +import type { ToolResponse } from '@/tools/types' + +export interface EmailBisonBaseParams { + apiKey: string +} + +export interface EmailBisonCustomVariable { + name: string + value: string | null +} + +export interface EmailBisonLeadStats { + emails_sent: number + opens: number + replies: number + unique_replies: number + unique_opens: number +} + +export interface EmailBisonLead { + id: number + first_name: string + last_name: string + email: string + title: string | null + company: string | null + notes: string | null + status: string | null + custom_variables: EmailBisonCustomVariable[] + lead_campaign_data: unknown[] + overall_stats: EmailBisonLeadStats + created_at: string | null + updated_at: string | null +} + +export interface EmailBisonTag { + id: number + name: string + default: boolean + created_at?: string | null + updated_at?: string | null +} + +export interface EmailBisonCampaignTag { + id: number + name: string + default: boolean +} + +export interface EmailBisonCampaign { + id: number + uuid: string | null + name: string + type: string | null + status: string | null + emails_sent: number + opened: number + unique_opens: number + replied: number + unique_replies: number + bounced: number + unsubscribed: number + interested: number + total_leads_contacted: number + total_leads: number + completion_percentage?: number | null + max_emails_per_day: number | null + max_new_leads_per_day: number | null + plain_text: boolean | null + open_tracking: boolean | null + can_unsubscribe: boolean | null + unsubscribe_text: string | null + sequence_prioritization?: string | null + tags: EmailBisonCampaignTag[] + created_at: string | null + updated_at: string | null +} + +export interface EmailBisonReplyAddress { + name: string | null + address: string +} + +export interface EmailBisonReplyAttachment { + id: number + uuid: string | null + reply_id: number | null + file_name: string | null + download_url: string | null + created_at: string | null + updated_at: string | null +} + +export interface EmailBisonReply { + id: number + uuid: string | null + folder: string | null + subject: string | null + read: boolean + interested: boolean + automated_reply: boolean + html_body: string | null + text_body: string | null + raw_body: string | null + headers: string | null + date_received: string | null + type: string | null + tracked_reply: boolean + scheduled_email_id: number | string | null + campaign_id: number | string | null + lead_id: number | null + sender_email_id: number | null + raw_message_id: string | null + from_name: string | null + from_email_address: string | null + primary_to_email_address: string | null + to: EmailBisonReplyAddress[] + cc: string | null + bcc: string | null + parent_id: number | string | null + attachments: EmailBisonReplyAttachment[] + created_at: string | null + updated_at: string | null +} + +export interface EmailBisonListLeadsParams extends EmailBisonBaseParams { + search?: string + campaignStatus?: string + tagIds?: number[] + excludedTagIds?: number[] + withoutTags?: boolean +} + +export interface EmailBisonGetLeadParams extends EmailBisonBaseParams { + leadId: string +} + +export interface EmailBisonLeadMutationParams extends EmailBisonBaseParams { + leadId?: string + firstName: string + lastName: string + email: string + title?: string + company?: string + notes?: string + customVariables?: EmailBisonCustomVariable[] +} + +export interface EmailBisonCreateCampaignParams extends EmailBisonBaseParams { + name: string + campaignType?: 'outbound' | 'reply_followup' +} + +export interface EmailBisonUpdateCampaignParams extends EmailBisonBaseParams { + campaignId: number + name?: string + maxEmailsPerDay?: number + maxNewLeadsPerDay?: number + plainText?: boolean + openTracking?: boolean + reputationBuilding?: boolean + canUnsubscribe?: boolean + includeAutoRepliesInStats?: boolean + sequencePrioritization?: 'followups' | 'new_leads' +} + +export interface EmailBisonCampaignStatusParams extends EmailBisonBaseParams { + campaignId: number + action: 'pause' | 'resume' | 'archive' +} + +export interface EmailBisonAttachLeadsParams extends EmailBisonBaseParams { + campaignId: number + leadIds: number[] + allowParallelSending?: boolean +} + +export interface EmailBisonListRepliesParams extends EmailBisonBaseParams { + search?: string + status?: string + folder?: string + read?: boolean + campaignId?: number + senderEmailId?: number + leadId?: number + tagIds?: number[] +} + +export interface EmailBisonCreateTagParams extends EmailBisonBaseParams { + name: string +} + +export interface EmailBisonAttachTagsToLeadsParams extends EmailBisonBaseParams { + tagIds: number[] + leadIds: number[] + skipWebhooks?: boolean +} + +export interface EmailBisonListLeadsResponse extends ToolResponse { + output: { + leads: EmailBisonLead[] + count: number + } +} + +export interface EmailBisonLeadResponse extends ToolResponse { + output: EmailBisonLead +} + +export interface EmailBisonListCampaignsResponse extends ToolResponse { + output: { + campaigns: EmailBisonCampaign[] + count: number + } +} + +export interface EmailBisonCampaignResponse extends ToolResponse { + output: EmailBisonCampaign +} + +export interface EmailBisonActionResponse extends ToolResponse { + output: { + success: boolean + message: string | null + } +} + +export interface EmailBisonListRepliesResponse extends ToolResponse { + output: { + replies: EmailBisonReply[] + count: number + } +} + +export interface EmailBisonListTagsResponse extends ToolResponse { + output: { + tags: EmailBisonTag[] + count: number + } +} + +export interface EmailBisonTagResponse extends ToolResponse { + output: EmailBisonTag +} + +export type EmailBisonResponse = + | EmailBisonListLeadsResponse + | EmailBisonLeadResponse + | EmailBisonListCampaignsResponse + | EmailBisonCampaignResponse + | EmailBisonActionResponse + | EmailBisonListRepliesResponse + | EmailBisonListTagsResponse + | EmailBisonTagResponse diff --git a/apps/sim/tools/emailbison/update_campaign.ts b/apps/sim/tools/emailbison/update_campaign.ts new file mode 100644 index 00000000000..1af6721ed92 --- /dev/null +++ b/apps/sim/tools/emailbison/update_campaign.ts @@ -0,0 +1,117 @@ +import type { + EmailBisonCampaignResponse, + EmailBisonUpdateCampaignParams, +} from '@/tools/emailbison/types' +import { + campaignOutputs, + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + jsonBody, + mapCampaign, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const updateCampaignTool: ToolConfig< + EmailBisonUpdateCampaignParams, + EmailBisonCampaignResponse +> = { + id: 'emailbison_update_campaign', + name: 'Email Bison Update Campaign', + description: 'Updates Email Bison campaign settings.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + campaignId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Campaign ID', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Campaign name', + }, + maxEmailsPerDay: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum emails per day', + }, + maxNewLeadsPerDay: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum new leads per day', + }, + plainText: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Send plain text emails', + }, + openTracking: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Enable open tracking', + }, + reputationBuilding: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Enable reputation building', + }, + canUnsubscribe: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Enable unsubscribe link', + }, + includeAutoRepliesInStats: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Include auto replies in campaign stats', + }, + sequencePrioritization: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sequence prioritization: followups or new_leads', + }, + }, + request: { + url: (params) => emailBisonUrl(`/api/campaigns/${params.campaignId}/update`), + method: 'PATCH', + headers: emailBisonHeaders, + body: (params) => + jsonBody({ + name: params.name, + max_emails_per_day: params.maxEmailsPerDay, + max_new_leads_per_day: params.maxNewLeadsPerDay, + plain_text: params.plainText, + open_tracking: params.openTracking, + reputation_building: params.reputationBuilding, + can_unsubscribe: params.canUnsubscribe, + include_auto_replies_in_stats: params.includeAutoRepliesInStats, + sequence_prioritization: params.sequencePrioritization, + }), + }, + transformResponse: async (response) => { + const data = await emailBisonData(response) + + return { + success: true, + output: mapCampaign(data), + } + }, + outputs: campaignOutputs, +} diff --git a/apps/sim/tools/emailbison/update_campaign_status.ts b/apps/sim/tools/emailbison/update_campaign_status.ts new file mode 100644 index 00000000000..532640f5b31 --- /dev/null +++ b/apps/sim/tools/emailbison/update_campaign_status.ts @@ -0,0 +1,56 @@ +import type { + EmailBisonCampaignResponse, + EmailBisonCampaignStatusParams, +} from '@/tools/emailbison/types' +import { + campaignOutputs, + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + mapCampaign, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const updateCampaignStatusTool: ToolConfig< + EmailBisonCampaignStatusParams, + EmailBisonCampaignResponse +> = { + id: 'emailbison_update_campaign_status', + name: 'Email Bison Update Campaign Status', + description: 'Pauses, resumes, or archives an Email Bison campaign.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + campaignId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Campaign ID', + }, + action: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Status action: pause, resume, or archive', + }, + }, + request: { + url: (params) => emailBisonUrl(`/api/campaigns/${params.campaignId}/${params.action}`), + method: 'PATCH', + headers: emailBisonHeaders, + }, + transformResponse: async (response) => { + const data = await emailBisonData(response) + + return { + success: true, + output: mapCampaign(data), + } + }, + outputs: campaignOutputs, +} diff --git a/apps/sim/tools/emailbison/update_lead.ts b/apps/sim/tools/emailbison/update_lead.ts new file mode 100644 index 00000000000..622a9dcce9e --- /dev/null +++ b/apps/sim/tools/emailbison/update_lead.ts @@ -0,0 +1,105 @@ +import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types' +import { + emailBisonData, + emailBisonHeaders, + emailBisonUrl, + jsonBody, + leadOutputs, + mapLead, +} from '@/tools/emailbison/utils' +import type { ToolConfig } from '@/tools/types' + +export const updateLeadTool: ToolConfig = { + id: 'emailbison_update_lead', + name: 'Email Bison Update Lead', + description: + 'Updates an existing Email Bison lead. Fields omitted from a PUT update may be cleared by Email Bison.', + version: '1.0.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + leadId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Lead ID or email address', + }, + firstName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Lead first name', + }, + lastName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Lead last name', + }, + email: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Lead email address', + }, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Lead job title', + }, + company: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Lead company', + }, + notes: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Additional notes about the lead', + }, + customVariables: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Custom variables to store on the lead', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Custom variable name' }, + value: { type: 'string', description: 'Custom variable value' }, + }, + }, + }, + }, + request: { + url: (params) => emailBisonUrl(`/api/leads/${encodeURIComponent(params.leadId?.trim() ?? '')}`), + method: 'PUT', + headers: emailBisonHeaders, + body: (params) => + jsonBody({ + first_name: params.firstName, + last_name: params.lastName, + email: params.email, + title: params.title, + company: params.company, + notes: params.notes, + custom_variables: params.customVariables, + }), + }, + transformResponse: async (response) => { + const data = await emailBisonData(response) + + return { + success: true, + output: mapLead(data), + } + }, + outputs: leadOutputs, +} diff --git a/apps/sim/tools/emailbison/utils.ts b/apps/sim/tools/emailbison/utils.ts new file mode 100644 index 00000000000..f624e87d6e3 --- /dev/null +++ b/apps/sim/tools/emailbison/utils.ts @@ -0,0 +1,445 @@ +import type { + EmailBisonBaseParams, + EmailBisonCampaign, + EmailBisonCampaignTag, + EmailBisonLead, + EmailBisonLeadStats, + EmailBisonReply, + EmailBisonReplyAddress, + EmailBisonReplyAttachment, + EmailBisonTag, +} from '@/tools/emailbison/types' +import type { OutputProperty, ToolConfig } from '@/tools/types' + +const EMAILBISON_API_BASE_URL = 'https://dedi.emailbison.com' + +type QueryValue = string | number | boolean | Array | undefined | null + +interface EmailBisonEnvelope { + data?: T +} + +export function emailBisonHeaders(params: EmailBisonBaseParams): Record { + return { + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + } +} + +export function emailBisonUrl(path: string, query: Record = {}): string { + const url = new URL(path, EMAILBISON_API_BASE_URL) + + Object.entries(query).forEach(([key, value]) => { + if (value === undefined || value === null || value === '') return + + if (Array.isArray(value)) { + value.forEach((item) => { + url.searchParams.append(key, String(item)) + }) + return + } + + url.searchParams.set(key, String(value)) + }) + + return url.toString() +} + +export function jsonBody(fields: Record): Record { + return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined)) +} + +export async function emailBisonData(response: Response): Promise { + const payload = (await response.json()) as EmailBisonEnvelope + return payload.data ?? null +} + +export function mapLead(value: unknown): EmailBisonLead { + const record = toRecord(value) + const stats = toRecord(record.overall_stats) + + return { + id: toNumber(record.id), + first_name: toStringValue(record.first_name), + last_name: toStringValue(record.last_name), + email: toStringValue(record.email), + title: toStringOrNull(record.title), + company: toStringOrNull(record.company), + notes: toStringOrNull(record.notes), + status: toStringOrNull(record.status), + custom_variables: toArray(record.custom_variables).map((item) => { + const variable = toRecord(item) + return { + name: toStringValue(variable.name), + value: toStringOrNull(variable.value), + } + }), + lead_campaign_data: toArray(record.lead_campaign_data), + overall_stats: mapLeadStats(stats), + created_at: toStringOrNull(record.created_at), + updated_at: toStringOrNull(record.updated_at), + } +} + +export function mapCampaign(value: unknown): EmailBisonCampaign { + const record = toRecord(value) + + return { + id: toNumber(record.id), + uuid: toStringOrNull(record.uuid), + name: toStringValue(record.name), + type: toStringOrNull(record.type), + status: toStringOrNull(record.status), + emails_sent: toNumber(record.emails_sent), + opened: toNumber(record.opened), + unique_opens: toNumber(record.unique_opens), + replied: toNumber(record.replied), + unique_replies: toNumber(record.unique_replies), + bounced: toNumber(record.bounced), + unsubscribed: toNumber(record.unsubscribed), + interested: toNumber(record.interested), + total_leads_contacted: toNumber(record.total_leads_contacted), + total_leads: toNumber(record.total_leads), + ...(record.completion_percentage !== undefined && { + completion_percentage: toNullableNumber(record.completion_percentage), + }), + max_emails_per_day: toNullableNumber(record.max_emails_per_day), + max_new_leads_per_day: toNullableNumber(record.max_new_leads_per_day), + plain_text: toNullableBoolean(record.plain_text), + open_tracking: toNullableBoolean(record.open_tracking), + can_unsubscribe: toNullableBoolean(record.can_unsubscribe), + unsubscribe_text: toStringOrNull(record.unsubscribe_text), + ...(record.sequence_prioritization !== undefined && { + sequence_prioritization: toStringOrNull(record.sequence_prioritization), + }), + tags: toArray(record.tags).map(mapCampaignTag), + created_at: toStringOrNull(record.created_at), + updated_at: toStringOrNull(record.updated_at), + } +} + +function mapCampaignTag(value: unknown): EmailBisonCampaignTag { + const record = toRecord(value) + + return { + id: toNumber(record.id), + name: toStringValue(record.name), + default: toBoolean(record.default), + } +} + +export function mapTag(value: unknown): EmailBisonTag { + const record = toRecord(value) + + return { + id: toNumber(record.id), + name: toStringValue(record.name), + default: toBoolean(record.default), + created_at: toStringOrNull(record.created_at), + updated_at: toStringOrNull(record.updated_at), + } +} + +export function mapReply(value: unknown): EmailBisonReply { + const record = toRecord(value) + + return { + id: toNumber(record.id), + uuid: toStringOrNull(record.uuid), + folder: toStringOrNull(record.folder), + subject: toStringOrNull(record.subject), + read: toBoolean(record.read), + interested: toBoolean(record.interested), + automated_reply: toBoolean(record.automated_reply), + html_body: toStringOrNull(record.html_body), + text_body: toStringOrNull(record.text_body), + raw_body: toStringOrNull(record.raw_body), + headers: toStringOrNull(record.headers), + date_received: toStringOrNull(record.date_received), + type: toStringOrNull(record.type), + tracked_reply: toBoolean(record.tracked_reply), + scheduled_email_id: toStringNumberOrNull(record.scheduled_email_id), + campaign_id: toStringNumberOrNull(record.campaign_id), + lead_id: toNullableNumber(record.lead_id), + sender_email_id: toNullableNumber(record.sender_email_id), + raw_message_id: toStringOrNull(record.raw_message_id), + from_name: toStringOrNull(record.from_name), + from_email_address: toStringOrNull(record.from_email_address), + primary_to_email_address: toStringOrNull(record.primary_to_email_address), + to: toArray(record.to).map(mapReplyAddress), + cc: toStringOrNull(record.cc), + bcc: toStringOrNull(record.bcc), + parent_id: toStringNumberOrNull(record.parent_id), + attachments: toArray(record.attachments).map(mapReplyAttachment), + created_at: toStringOrNull(record.created_at), + updated_at: toStringOrNull(record.updated_at), + } +} + +export function actionOutput(value: unknown): { success: boolean; message: string | null } { + const record = toRecord(value) + + return { + success: toBoolean(record.success), + message: toStringOrNull(record.message), + } +} + +export const leadOutputs = { + id: { type: 'number', description: 'Lead ID' }, + first_name: { type: 'string', description: 'Lead first name' }, + last_name: { type: 'string', description: 'Lead last name' }, + email: { type: 'string', description: 'Lead email address' }, + title: { type: 'string', description: 'Lead title', optional: true }, + company: { type: 'string', description: 'Lead company', optional: true }, + notes: { type: 'string', description: 'Lead notes', optional: true }, + status: { type: 'string', description: 'Lead status', optional: true }, + custom_variables: { + type: 'array', + description: 'Lead custom variables', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Custom variable name' }, + value: { type: 'string', description: 'Custom variable value', optional: true }, + }, + }, + }, + lead_campaign_data: { + type: 'array', + description: 'Lead campaign data returned by Email Bison', + }, + overall_stats: { + type: 'object', + description: 'Lead engagement stats', + properties: { + emails_sent: { type: 'number', description: 'Emails sent' }, + opens: { type: 'number', description: 'Email opens' }, + replies: { type: 'number', description: 'Replies' }, + unique_replies: { type: 'number', description: 'Unique replies' }, + unique_opens: { type: 'number', description: 'Unique opens' }, + }, + }, + created_at: { type: 'string', description: 'Lead creation timestamp', optional: true }, + updated_at: { type: 'string', description: 'Lead update timestamp', optional: true }, +} satisfies NonNullable + +const tagProperties = { + id: { type: 'number', description: 'Tag ID' }, + name: { type: 'string', description: 'Tag name' }, + default: { type: 'boolean', description: 'Whether this is a default tag' }, + created_at: { type: 'string', description: 'Tag creation timestamp', optional: true }, + updated_at: { type: 'string', description: 'Tag update timestamp', optional: true }, +} satisfies Record + +const campaignTagProperties = { + id: { type: 'number', description: 'Tag ID' }, + name: { type: 'string', description: 'Tag name' }, + default: { type: 'boolean', description: 'Whether this is a default tag' }, +} satisfies Record + +export const listLeadsOutputs = { + leads: { + type: 'array', + description: 'List of leads', + items: { + type: 'object', + properties: leadOutputs, + }, + }, + count: { type: 'number', description: 'Number of leads returned' }, +} satisfies NonNullable + +export const campaignOutputs = { + id: { type: 'number', description: 'Campaign ID' }, + uuid: { type: 'string', description: 'Campaign UUID', optional: true }, + name: { type: 'string', description: 'Campaign name' }, + type: { type: 'string', description: 'Campaign type', optional: true }, + status: { type: 'string', description: 'Campaign status', optional: true }, + emails_sent: { type: 'number', description: 'Emails sent' }, + opened: { type: 'number', description: 'Total opens' }, + unique_opens: { type: 'number', description: 'Unique opens' }, + replied: { type: 'number', description: 'Total replies' }, + unique_replies: { type: 'number', description: 'Unique replies' }, + bounced: { type: 'number', description: 'Bounces' }, + unsubscribed: { type: 'number', description: 'Unsubscribes' }, + interested: { type: 'number', description: 'Interested replies' }, + total_leads_contacted: { type: 'number', description: 'Total leads contacted' }, + total_leads: { type: 'number', description: 'Total leads' }, + completion_percentage: { + type: 'number', + description: 'Campaign completion percentage', + optional: true, + }, + max_emails_per_day: { type: 'number', description: 'Maximum emails per day', optional: true }, + max_new_leads_per_day: { + type: 'number', + description: 'Maximum new leads per day', + optional: true, + }, + plain_text: { + type: 'boolean', + description: 'Whether campaign emails are plain text', + optional: true, + }, + open_tracking: { + type: 'boolean', + description: 'Whether open tracking is enabled', + optional: true, + }, + can_unsubscribe: { + type: 'boolean', + description: 'Whether recipients can unsubscribe', + optional: true, + }, + unsubscribe_text: { type: 'string', description: 'Unsubscribe text', optional: true }, + tags: { + type: 'array', + description: 'Campaign tags', + items: { type: 'object', properties: campaignTagProperties }, + }, + created_at: { type: 'string', description: 'Campaign creation timestamp', optional: true }, + updated_at: { type: 'string', description: 'Campaign update timestamp', optional: true }, +} satisfies NonNullable + +export const listCampaignsOutputs = { + campaigns: { + type: 'array', + description: 'List of campaigns', + items: { + type: 'object', + properties: campaignOutputs, + }, + }, + count: { type: 'number', description: 'Number of campaigns returned' }, +} satisfies NonNullable + +export const actionOutputs = { + success: { type: 'boolean', description: 'Whether the action succeeded' }, + message: { type: 'string', description: 'Action message', optional: true }, +} satisfies NonNullable + +export const listRepliesOutputs = { + replies: { + type: 'array', + description: 'List of replies', + items: { + type: 'object', + properties: { + id: { type: 'number', description: 'Reply ID' }, + subject: { type: 'string', description: 'Reply subject', optional: true }, + text_body: { type: 'string', description: 'Reply text body', optional: true }, + from_email_address: { type: 'string', description: 'Sender email', optional: true }, + primary_to_email_address: { + type: 'string', + description: 'Primary recipient', + optional: true, + }, + date_received: { type: 'string', description: 'Date received', optional: true }, + interested: { type: 'boolean', description: 'Whether the reply is marked interested' }, + read: { type: 'boolean', description: 'Whether the reply is read' }, + }, + }, + }, + count: { type: 'number', description: 'Number of replies returned' }, +} satisfies NonNullable + +export const tagOutputs = { + id: { type: 'number', description: 'Tag ID' }, + name: { type: 'string', description: 'Tag name' }, + default: { type: 'boolean', description: 'Whether this is a default tag' }, + created_at: { type: 'string', description: 'Tag creation timestamp', optional: true }, + updated_at: { type: 'string', description: 'Tag update timestamp', optional: true }, +} satisfies NonNullable + +export const listTagsOutputs = { + tags: { + type: 'array', + description: 'List of tags', + items: { + type: 'object', + properties: tagProperties, + }, + }, + count: { type: 'number', description: 'Number of tags returned' }, +} satisfies NonNullable + +function mapLeadStats(record: Record): EmailBisonLeadStats { + return { + emails_sent: toNumber(record.emails_sent), + opens: toNumber(record.opens), + replies: toNumber(record.replies), + unique_replies: toNumber(record.unique_replies), + unique_opens: toNumber(record.unique_opens), + } +} + +function mapReplyAddress(value: unknown): EmailBisonReplyAddress { + const record = toRecord(value) + + return { + name: toStringOrNull(record.name), + address: toStringValue(record.address), + } +} + +function mapReplyAttachment(value: unknown): EmailBisonReplyAttachment { + const record = toRecord(value) + + return { + id: toNumber(record.id), + uuid: toStringOrNull(record.uuid), + reply_id: toNullableNumber(record.reply_id), + file_name: toStringOrNull(record.file_name), + download_url: toStringOrNull(record.download_url), + created_at: toStringOrNull(record.created_at), + updated_at: toStringOrNull(record.updated_at), + } +} + +function toRecord(value: unknown): Record { + if (!value || typeof value !== 'object' || Array.isArray(value)) return {} + return value as Record +} + +function toArray(value: unknown): unknown[] { + return Array.isArray(value) ? value : [] +} + +function toStringValue(value: unknown): string { + if (value === undefined || value === null) return '' + return String(value) +} + +function toStringOrNull(value: unknown): string | null { + if (value === undefined || value === null) return null + return String(value) +} + +function toStringNumberOrNull(value: unknown): number | string | null { + if (typeof value === 'number' || typeof value === 'string') return value + return null +} + +function toNumber(value: unknown): number { + if (typeof value === 'number') return value + if (typeof value === 'string') { + const parsed = Number(value) + return Number.isFinite(parsed) ? parsed : 0 + } + return 0 +} + +function toNullableNumber(value: unknown): number | null { + if (value === undefined || value === null) return null + return toNumber(value) +} + +function toBoolean(value: unknown): boolean { + return typeof value === 'boolean' ? value : false +} + +function toNullableBoolean(value: unknown): boolean | null { + if (value === undefined || value === null) return null + return toBoolean(value) +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 9130ac52dee..4b5bca95866 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -566,6 +566,21 @@ import { elasticsearchUpdateDocumentTool, } from '@/tools/elasticsearch' import { elevenLabsTtsTool } from '@/tools/elevenlabs' +import { + emailBisonAttachLeadsToCampaignTool, + emailBisonAttachTagsToLeadsTool, + emailBisonCreateCampaignTool, + emailBisonCreateLeadTool, + emailBisonCreateTagTool, + emailBisonGetLeadTool, + emailBisonListCampaignsTool, + emailBisonListLeadsTool, + emailBisonListRepliesTool, + emailBisonListTagsTool, + emailBisonUpdateCampaignStatusTool, + emailBisonUpdateCampaignTool, + emailBisonUpdateLeadTool, +} from '@/tools/emailbison' import { enrichCheckCreditsTool, enrichCompanyFundingTool, @@ -4014,6 +4029,19 @@ export const tools: Record = { elasticsearch_list_indices: elasticsearchListIndicesTool, elasticsearch_cluster_health: elasticsearchClusterHealthTool, elasticsearch_cluster_stats: elasticsearchClusterStatsTool, + emailbison_attach_leads_to_campaign: emailBisonAttachLeadsToCampaignTool, + emailbison_attach_tags_to_leads: emailBisonAttachTagsToLeadsTool, + emailbison_create_campaign: emailBisonCreateCampaignTool, + emailbison_create_lead: emailBisonCreateLeadTool, + emailbison_create_tag: emailBisonCreateTagTool, + emailbison_get_lead: emailBisonGetLeadTool, + emailbison_list_campaigns: emailBisonListCampaignsTool, + emailbison_list_leads: emailBisonListLeadsTool, + emailbison_list_replies: emailBisonListRepliesTool, + emailbison_list_tags: emailBisonListTagsTool, + emailbison_update_campaign: emailBisonUpdateCampaignTool, + emailbison_update_campaign_status: emailBisonUpdateCampaignStatusTool, + emailbison_update_lead: emailBisonUpdateLeadTool, evernote_copy_note: evernoteCopyNoteTool, evernote_create_note: evernoteCreateNoteTool, evernote_create_notebook: evernoteCreateNotebookTool, From 63e79fcb3b9f29afc6f3d89870d08bb2f651a6d4 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 5 May 2026 23:19:35 -0700 Subject: [PATCH 2/7] type improvments --- .../emailbison/attach_leads_to_campaign.ts | 4 +- .../tools/emailbison/attach_tags_to_leads.ts | 4 +- apps/sim/tools/emailbison/create_campaign.ts | 4 +- apps/sim/tools/emailbison/create_lead.ts | 4 +- apps/sim/tools/emailbison/create_tag.ts | 4 +- apps/sim/tools/emailbison/get_lead.ts | 4 +- apps/sim/tools/emailbison/list_campaigns.ts | 6 +- apps/sim/tools/emailbison/list_leads.ts | 6 +- apps/sim/tools/emailbison/list_replies.ts | 6 +- apps/sim/tools/emailbison/list_tags.ts | 6 +- apps/sim/tools/emailbison/types.ts | 71 ++++++----- apps/sim/tools/emailbison/update_campaign.ts | 4 +- .../emailbison/update_campaign_status.ts | 4 +- apps/sim/tools/emailbison/update_lead.ts | 4 +- apps/sim/tools/emailbison/utils.ts | 113 +++++++++--------- 15 files changed, 121 insertions(+), 123 deletions(-) diff --git a/apps/sim/tools/emailbison/attach_leads_to_campaign.ts b/apps/sim/tools/emailbison/attach_leads_to_campaign.ts index a7ecd4e2c7f..b8151b66f04 100644 --- a/apps/sim/tools/emailbison/attach_leads_to_campaign.ts +++ b/apps/sim/tools/emailbison/attach_leads_to_campaign.ts @@ -5,8 +5,8 @@ import type { import { actionOutput, actionOutputs, - emailBisonData, emailBisonHeaders, + emailBisonRecordData, emailBisonUrl, jsonBody, } from '@/tools/emailbison/utils' @@ -58,7 +58,7 @@ export const attachLeadsToCampaignTool: ToolConfig< }), }, transformResponse: async (response) => { - const data = await emailBisonData(response) + const data = await emailBisonRecordData(response, 'campaign lead attachment result') return { success: true, diff --git a/apps/sim/tools/emailbison/attach_tags_to_leads.ts b/apps/sim/tools/emailbison/attach_tags_to_leads.ts index 93a6fd793da..1943016a1f5 100644 --- a/apps/sim/tools/emailbison/attach_tags_to_leads.ts +++ b/apps/sim/tools/emailbison/attach_tags_to_leads.ts @@ -5,8 +5,8 @@ import type { import { actionOutput, actionOutputs, - emailBisonData, emailBisonHeaders, + emailBisonRecordData, emailBisonUrl, jsonBody, } from '@/tools/emailbison/utils' @@ -60,7 +60,7 @@ export const attachTagsToLeadsTool: ToolConfig< }), }, transformResponse: async (response) => { - const data = await emailBisonData(response) + const data = await emailBisonRecordData(response, 'tag attachment result') return { success: true, diff --git a/apps/sim/tools/emailbison/create_campaign.ts b/apps/sim/tools/emailbison/create_campaign.ts index a2971eaa03b..897de0e90d7 100644 --- a/apps/sim/tools/emailbison/create_campaign.ts +++ b/apps/sim/tools/emailbison/create_campaign.ts @@ -4,8 +4,8 @@ import type { } from '@/tools/emailbison/types' import { campaignOutputs, - emailBisonData, emailBisonHeaders, + emailBisonRecordData, emailBisonUrl, jsonBody, mapCampaign, @@ -51,7 +51,7 @@ export const createCampaignTool: ToolConfig< }), }, transformResponse: async (response) => { - const data = await emailBisonData(response) + const data = await emailBisonRecordData(response, 'campaign') return { success: true, diff --git a/apps/sim/tools/emailbison/create_lead.ts b/apps/sim/tools/emailbison/create_lead.ts index 267a9de0af8..1cbf256f579 100644 --- a/apps/sim/tools/emailbison/create_lead.ts +++ b/apps/sim/tools/emailbison/create_lead.ts @@ -1,7 +1,7 @@ import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types' import { - emailBisonData, emailBisonHeaders, + emailBisonRecordData, emailBisonUrl, jsonBody, leadOutputs, @@ -87,7 +87,7 @@ export const createLeadTool: ToolConfig { - const data = await emailBisonData(response) + const data = await emailBisonRecordData(response, 'lead') return { success: true, diff --git a/apps/sim/tools/emailbison/create_tag.ts b/apps/sim/tools/emailbison/create_tag.ts index 2c12e1788c9..6488483895c 100644 --- a/apps/sim/tools/emailbison/create_tag.ts +++ b/apps/sim/tools/emailbison/create_tag.ts @@ -1,7 +1,7 @@ import type { EmailBisonCreateTagParams, EmailBisonTagResponse } from '@/tools/emailbison/types' import { - emailBisonData, emailBisonHeaders, + emailBisonRecordData, emailBisonUrl, jsonBody, mapTag, @@ -35,7 +35,7 @@ export const createTagTool: ToolConfig jsonBody({ name: params.name }), }, transformResponse: async (response) => { - const data = await emailBisonData(response) + const data = await emailBisonRecordData(response, 'tag') return { success: true, diff --git a/apps/sim/tools/emailbison/get_lead.ts b/apps/sim/tools/emailbison/get_lead.ts index fc5d51ed67d..90a3fb73fa7 100644 --- a/apps/sim/tools/emailbison/get_lead.ts +++ b/apps/sim/tools/emailbison/get_lead.ts @@ -1,7 +1,7 @@ import type { EmailBisonGetLeadParams, EmailBisonLeadResponse } from '@/tools/emailbison/types' import { - emailBisonData, emailBisonHeaders, + emailBisonRecordData, emailBisonUrl, leadOutputs, mapLead, @@ -33,7 +33,7 @@ export const getLeadTool: ToolConfig { - const data = await emailBisonData(response) + const data = await emailBisonRecordData(response, 'lead') return { success: true, diff --git a/apps/sim/tools/emailbison/list_campaigns.ts b/apps/sim/tools/emailbison/list_campaigns.ts index 5014e84cd09..1dc3093c708 100644 --- a/apps/sim/tools/emailbison/list_campaigns.ts +++ b/apps/sim/tools/emailbison/list_campaigns.ts @@ -3,7 +3,7 @@ import type { EmailBisonListCampaignsResponse, } from '@/tools/emailbison/types' import { - emailBisonData, + emailBisonArrayData, emailBisonHeaders, emailBisonUrl, listCampaignsOutputs, @@ -31,8 +31,8 @@ export const listCampaignsTool: ToolConfig { - const data = (await emailBisonData(response)) ?? [] - const campaigns = Array.isArray(data) ? data.map(mapCampaign) : [] + const data = await emailBisonArrayData(response, 'campaigns') + const campaigns = data.map(mapCampaign) return { success: true, diff --git a/apps/sim/tools/emailbison/list_leads.ts b/apps/sim/tools/emailbison/list_leads.ts index 8d9368a1946..b12e7fb3d7f 100644 --- a/apps/sim/tools/emailbison/list_leads.ts +++ b/apps/sim/tools/emailbison/list_leads.ts @@ -3,7 +3,7 @@ import type { EmailBisonListLeadsResponse, } from '@/tools/emailbison/types' import { - emailBisonData, + emailBisonArrayData, emailBisonHeaders, emailBisonUrl, listLeadsOutputs, @@ -70,8 +70,8 @@ export const listLeadsTool: ToolConfig { - const data = (await emailBisonData(response)) ?? [] - const leads = Array.isArray(data) ? data.map(mapLead) : [] + const data = await emailBisonArrayData(response, 'leads') + const leads = data.map(mapLead) return { success: true, diff --git a/apps/sim/tools/emailbison/list_replies.ts b/apps/sim/tools/emailbison/list_replies.ts index 00bbd913602..6f075c5e188 100644 --- a/apps/sim/tools/emailbison/list_replies.ts +++ b/apps/sim/tools/emailbison/list_replies.ts @@ -3,7 +3,7 @@ import type { EmailBisonListRepliesResponse, } from '@/tools/emailbison/types' import { - emailBisonData, + emailBisonArrayData, emailBisonHeaders, emailBisonUrl, listRepliesOutputs, @@ -93,8 +93,8 @@ export const listRepliesTool: ToolConfig< headers: emailBisonHeaders, }, transformResponse: async (response) => { - const data = (await emailBisonData(response)) ?? [] - const replies = Array.isArray(data) ? data.map(mapReply) : [] + const data = await emailBisonArrayData(response, 'replies') + const replies = data.map(mapReply) return { success: true, diff --git a/apps/sim/tools/emailbison/list_tags.ts b/apps/sim/tools/emailbison/list_tags.ts index 680c58093a1..747ab53e277 100644 --- a/apps/sim/tools/emailbison/list_tags.ts +++ b/apps/sim/tools/emailbison/list_tags.ts @@ -1,6 +1,6 @@ import type { EmailBisonBaseParams, EmailBisonListTagsResponse } from '@/tools/emailbison/types' import { - emailBisonData, + emailBisonArrayData, emailBisonHeaders, emailBisonUrl, listTagsOutputs, @@ -27,8 +27,8 @@ export const listTagsTool: ToolConfig { - const data = (await emailBisonData(response)) ?? [] - const tags = Array.isArray(data) ? data.map(mapTag) : [] + const data = await emailBisonArrayData(response, 'tags') + const tags = data.map(mapTag) return { success: true, diff --git a/apps/sim/tools/emailbison/types.ts b/apps/sim/tools/emailbison/types.ts index f66782fe807..2b357f446ae 100644 --- a/apps/sim/tools/emailbison/types.ts +++ b/apps/sim/tools/emailbison/types.ts @@ -5,23 +5,23 @@ export interface EmailBisonBaseParams { } export interface EmailBisonCustomVariable { - name: string + name: string | null value: string | null } export interface EmailBisonLeadStats { - emails_sent: number - opens: number - replies: number - unique_replies: number - unique_opens: number + emails_sent: number | null + opens: number | null + replies: number | null + unique_replies: number | null + unique_opens: number | null } export interface EmailBisonLead { - id: number - first_name: string - last_name: string - email: string + id: number | null + first_name: string | null + last_name: string | null + email: string | null title: string | null company: string | null notes: string | null @@ -34,36 +34,35 @@ export interface EmailBisonLead { } export interface EmailBisonTag { - id: number - name: string - default: boolean + id: number | null + name: string | null + default: boolean | null created_at?: string | null updated_at?: string | null } export interface EmailBisonCampaignTag { - id: number - name: string - default: boolean + id: number | null + name: string | null + default: boolean | null } export interface EmailBisonCampaign { - id: number + id: number | null uuid: string | null - name: string + name: string | null type: string | null status: string | null - emails_sent: number - opened: number - unique_opens: number - replied: number - unique_replies: number - bounced: number - unsubscribed: number - interested: number - total_leads_contacted: number - total_leads: number - completion_percentage?: number | null + emails_sent: number | null + opened: number | null + unique_opens: number | null + replied: number | null + unique_replies: number | null + bounced: number | null + unsubscribed: number | null + interested: number | null + total_leads_contacted: number | null + total_leads: number | null max_emails_per_day: number | null max_new_leads_per_day: number | null plain_text: boolean | null @@ -78,11 +77,11 @@ export interface EmailBisonCampaign { export interface EmailBisonReplyAddress { name: string | null - address: string + address: string | null } export interface EmailBisonReplyAttachment { - id: number + id: number | null uuid: string | null reply_id: number | null file_name: string | null @@ -92,20 +91,20 @@ export interface EmailBisonReplyAttachment { } export interface EmailBisonReply { - id: number + id: number | null uuid: string | null folder: string | null subject: string | null - read: boolean - interested: boolean - automated_reply: boolean + read: boolean | null + interested: boolean | null + automated_reply: boolean | null html_body: string | null text_body: string | null raw_body: string | null headers: string | null date_received: string | null type: string | null - tracked_reply: boolean + tracked_reply: boolean | null scheduled_email_id: number | string | null campaign_id: number | string | null lead_id: number | null diff --git a/apps/sim/tools/emailbison/update_campaign.ts b/apps/sim/tools/emailbison/update_campaign.ts index 1af6721ed92..48ea126b10c 100644 --- a/apps/sim/tools/emailbison/update_campaign.ts +++ b/apps/sim/tools/emailbison/update_campaign.ts @@ -4,8 +4,8 @@ import type { } from '@/tools/emailbison/types' import { campaignOutputs, - emailBisonData, emailBisonHeaders, + emailBisonRecordData, emailBisonUrl, jsonBody, mapCampaign, @@ -106,7 +106,7 @@ export const updateCampaignTool: ToolConfig< }), }, transformResponse: async (response) => { - const data = await emailBisonData(response) + const data = await emailBisonRecordData(response, 'campaign') return { success: true, diff --git a/apps/sim/tools/emailbison/update_campaign_status.ts b/apps/sim/tools/emailbison/update_campaign_status.ts index 532640f5b31..61fbc5dab8e 100644 --- a/apps/sim/tools/emailbison/update_campaign_status.ts +++ b/apps/sim/tools/emailbison/update_campaign_status.ts @@ -4,8 +4,8 @@ import type { } from '@/tools/emailbison/types' import { campaignOutputs, - emailBisonData, emailBisonHeaders, + emailBisonRecordData, emailBisonUrl, mapCampaign, } from '@/tools/emailbison/utils' @@ -45,7 +45,7 @@ export const updateCampaignStatusTool: ToolConfig< headers: emailBisonHeaders, }, transformResponse: async (response) => { - const data = await emailBisonData(response) + const data = await emailBisonRecordData(response, 'campaign') return { success: true, diff --git a/apps/sim/tools/emailbison/update_lead.ts b/apps/sim/tools/emailbison/update_lead.ts index 622a9dcce9e..7db7edb6fe1 100644 --- a/apps/sim/tools/emailbison/update_lead.ts +++ b/apps/sim/tools/emailbison/update_lead.ts @@ -1,7 +1,7 @@ import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types' import { - emailBisonData, emailBisonHeaders, + emailBisonRecordData, emailBisonUrl, jsonBody, leadOutputs, @@ -94,7 +94,7 @@ export const updateLeadTool: ToolConfig { - const data = await emailBisonData(response) + const data = await emailBisonRecordData(response, 'lead') return { success: true, diff --git a/apps/sim/tools/emailbison/utils.ts b/apps/sim/tools/emailbison/utils.ts index f624e87d6e3..daf4a3e70d9 100644 --- a/apps/sim/tools/emailbison/utils.ts +++ b/apps/sim/tools/emailbison/utils.ts @@ -54,15 +54,31 @@ export async function emailBisonData(response: Response): Promise { return payload.data ?? null } +export async function emailBisonArrayData(response: Response, label: string): Promise { + const data = await emailBisonData(response) + if (!Array.isArray(data)) { + throw new Error(`Email Bison response did not include a valid ${label} array`) + } + return data +} + +export async function emailBisonRecordData(response: Response, label: string): Promise { + const data = await emailBisonData(response) + if (!isRecord(data)) { + throw new Error(`Email Bison response did not include a valid ${label} object`) + } + return data +} + export function mapLead(value: unknown): EmailBisonLead { const record = toRecord(value) const stats = toRecord(record.overall_stats) return { - id: toNumber(record.id), - first_name: toStringValue(record.first_name), - last_name: toStringValue(record.last_name), - email: toStringValue(record.email), + id: toNullableNumber(record.id), + first_name: toStringOrNull(record.first_name), + last_name: toStringOrNull(record.last_name), + email: toStringOrNull(record.email), title: toStringOrNull(record.title), company: toStringOrNull(record.company), notes: toStringOrNull(record.notes), @@ -70,7 +86,7 @@ export function mapLead(value: unknown): EmailBisonLead { custom_variables: toArray(record.custom_variables).map((item) => { const variable = toRecord(item) return { - name: toStringValue(variable.name), + name: toStringOrNull(variable.name), value: toStringOrNull(variable.value), } }), @@ -85,24 +101,21 @@ export function mapCampaign(value: unknown): EmailBisonCampaign { const record = toRecord(value) return { - id: toNumber(record.id), + id: toNullableNumber(record.id), uuid: toStringOrNull(record.uuid), - name: toStringValue(record.name), + name: toStringOrNull(record.name), type: toStringOrNull(record.type), status: toStringOrNull(record.status), - emails_sent: toNumber(record.emails_sent), - opened: toNumber(record.opened), - unique_opens: toNumber(record.unique_opens), - replied: toNumber(record.replied), - unique_replies: toNumber(record.unique_replies), - bounced: toNumber(record.bounced), - unsubscribed: toNumber(record.unsubscribed), - interested: toNumber(record.interested), - total_leads_contacted: toNumber(record.total_leads_contacted), - total_leads: toNumber(record.total_leads), - ...(record.completion_percentage !== undefined && { - completion_percentage: toNullableNumber(record.completion_percentage), - }), + emails_sent: toNullableNumber(record.emails_sent), + opened: toNullableNumber(record.opened), + unique_opens: toNullableNumber(record.unique_opens), + replied: toNullableNumber(record.replied), + unique_replies: toNullableNumber(record.unique_replies), + bounced: toNullableNumber(record.bounced), + unsubscribed: toNullableNumber(record.unsubscribed), + interested: toNullableNumber(record.interested), + total_leads_contacted: toNullableNumber(record.total_leads_contacted), + total_leads: toNullableNumber(record.total_leads), max_emails_per_day: toNullableNumber(record.max_emails_per_day), max_new_leads_per_day: toNullableNumber(record.max_new_leads_per_day), plain_text: toNullableBoolean(record.plain_text), @@ -122,9 +135,9 @@ function mapCampaignTag(value: unknown): EmailBisonCampaignTag { const record = toRecord(value) return { - id: toNumber(record.id), - name: toStringValue(record.name), - default: toBoolean(record.default), + id: toNullableNumber(record.id), + name: toStringOrNull(record.name), + default: toNullableBoolean(record.default), } } @@ -132,9 +145,9 @@ export function mapTag(value: unknown): EmailBisonTag { const record = toRecord(value) return { - id: toNumber(record.id), - name: toStringValue(record.name), - default: toBoolean(record.default), + id: toNullableNumber(record.id), + name: toStringOrNull(record.name), + default: toNullableBoolean(record.default), created_at: toStringOrNull(record.created_at), updated_at: toStringOrNull(record.updated_at), } @@ -144,20 +157,20 @@ export function mapReply(value: unknown): EmailBisonReply { const record = toRecord(value) return { - id: toNumber(record.id), + id: toNullableNumber(record.id), uuid: toStringOrNull(record.uuid), folder: toStringOrNull(record.folder), subject: toStringOrNull(record.subject), - read: toBoolean(record.read), - interested: toBoolean(record.interested), - automated_reply: toBoolean(record.automated_reply), + read: toNullableBoolean(record.read), + interested: toNullableBoolean(record.interested), + automated_reply: toNullableBoolean(record.automated_reply), html_body: toStringOrNull(record.html_body), text_body: toStringOrNull(record.text_body), raw_body: toStringOrNull(record.raw_body), headers: toStringOrNull(record.headers), date_received: toStringOrNull(record.date_received), type: toStringOrNull(record.type), - tracked_reply: toBoolean(record.tracked_reply), + tracked_reply: toNullableBoolean(record.tracked_reply), scheduled_email_id: toStringNumberOrNull(record.scheduled_email_id), campaign_id: toStringNumberOrNull(record.campaign_id), lead_id: toNullableNumber(record.lead_id), @@ -180,7 +193,7 @@ export function actionOutput(value: unknown): { success: boolean; message: strin const record = toRecord(value) return { - success: toBoolean(record.success), + success: record.success === true, message: toStringOrNull(record.message), } } @@ -266,11 +279,6 @@ export const campaignOutputs = { interested: { type: 'number', description: 'Interested replies' }, total_leads_contacted: { type: 'number', description: 'Total leads contacted' }, total_leads: { type: 'number', description: 'Total leads' }, - completion_percentage: { - type: 'number', - description: 'Campaign completion percentage', - optional: true, - }, max_emails_per_day: { type: 'number', description: 'Maximum emails per day', optional: true }, max_new_leads_per_day: { type: 'number', @@ -366,11 +374,11 @@ export const listTagsOutputs = { function mapLeadStats(record: Record): EmailBisonLeadStats { return { - emails_sent: toNumber(record.emails_sent), - opens: toNumber(record.opens), - replies: toNumber(record.replies), - unique_replies: toNumber(record.unique_replies), - unique_opens: toNumber(record.unique_opens), + emails_sent: toNullableNumber(record.emails_sent), + opens: toNullableNumber(record.opens), + replies: toNullableNumber(record.replies), + unique_replies: toNullableNumber(record.unique_replies), + unique_opens: toNullableNumber(record.unique_opens), } } @@ -379,7 +387,7 @@ function mapReplyAddress(value: unknown): EmailBisonReplyAddress { return { name: toStringOrNull(record.name), - address: toStringValue(record.address), + address: toStringOrNull(record.address), } } @@ -387,7 +395,7 @@ function mapReplyAttachment(value: unknown): EmailBisonReplyAttachment { const record = toRecord(value) return { - id: toNumber(record.id), + id: toNullableNumber(record.id), uuid: toStringOrNull(record.uuid), reply_id: toNullableNumber(record.reply_id), file_name: toStringOrNull(record.file_name), @@ -406,11 +414,6 @@ function toArray(value: unknown): unknown[] { return Array.isArray(value) ? value : [] } -function toStringValue(value: unknown): string { - if (value === undefined || value === null) return '' - return String(value) -} - function toStringOrNull(value: unknown): string | null { if (value === undefined || value === null) return null return String(value) @@ -421,13 +424,13 @@ function toStringNumberOrNull(value: unknown): number | string | null { return null } -function toNumber(value: unknown): number { +function toNumber(value: unknown): number | null { if (typeof value === 'number') return value if (typeof value === 'string') { const parsed = Number(value) - return Number.isFinite(parsed) ? parsed : 0 + return Number.isFinite(parsed) ? parsed : null } - return 0 + return null } function toNullableNumber(value: unknown): number | null { @@ -435,11 +438,7 @@ function toNullableNumber(value: unknown): number | null { return toNumber(value) } -function toBoolean(value: unknown): boolean { - return typeof value === 'boolean' ? value : false -} - function toNullableBoolean(value: unknown): boolean | null { if (value === undefined || value === null) return null - return toBoolean(value) + return typeof value === 'boolean' ? value : null } From b3f6a0969f8741402c8188893a682dba3fec596f Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 5 May 2026 23:21:44 -0700 Subject: [PATCH 3/7] typecheck issue --- apps/sim/tools/emailbison/utils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/sim/tools/emailbison/utils.ts b/apps/sim/tools/emailbison/utils.ts index daf4a3e70d9..f14ba3bbcd6 100644 --- a/apps/sim/tools/emailbison/utils.ts +++ b/apps/sim/tools/emailbison/utils.ts @@ -405,9 +405,12 @@ function mapReplyAttachment(value: unknown): EmailBisonReplyAttachment { } } +function isRecord(value: unknown): value is Record { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value) +} + function toRecord(value: unknown): Record { - if (!value || typeof value !== 'object' || Array.isArray(value)) return {} - return value as Record + return isRecord(value) ? value : {} } function toArray(value: unknown): unknown[] { From 9a57601cda5cfefdf2b2b520093643d629b9a789 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 6 May 2026 12:16:11 -0700 Subject: [PATCH 4/7] add email bison trigger, cleanup sharepoint block --- apps/docs/components/ui/icon-mapping.ts | 1 + .../docs/content/docs/en/tools/emailbison.mdx | 13 - .../docs/content/docs/en/tools/sharepoint.mdx | 28 +- .../content/docs/en/triggers/emailbison.mdx | 1178 +++++++++++++++++ apps/docs/content/docs/en/triggers/meta.json | 1 + .../integrations/(shell)/[slug]/page.tsx | 11 +- .../integrations/data/icon-mapping.ts | 2 +- .../integrations/data/integrations.json | 8 +- .../app/api/tools/sharepoint/upload/route.ts | 46 +- apps/sim/blocks/blocks/emailbison.ts | 34 + apps/sim/blocks/blocks/sharepoint.ts | 476 +++++++ apps/sim/blocks/registry.ts | 3 +- apps/sim/lib/webhooks/providers/emailbison.ts | 300 +++++ apps/sim/lib/webhooks/providers/registry.ts | 2 + .../emailbison/attach_leads_to_campaign.ts | 15 +- .../tools/emailbison/attach_tags_to_leads.ts | 10 +- apps/sim/tools/emailbison/create_campaign.ts | 10 +- apps/sim/tools/emailbison/create_lead.ts | 10 +- apps/sim/tools/emailbison/create_tag.ts | 10 +- apps/sim/tools/emailbison/get_lead.ts | 15 +- apps/sim/tools/emailbison/list_campaigns.ts | 10 +- apps/sim/tools/emailbison/list_leads.ts | 26 +- apps/sim/tools/emailbison/list_replies.ts | 32 +- apps/sim/tools/emailbison/list_tags.ts | 10 +- apps/sim/tools/emailbison/types.ts | 1 + apps/sim/tools/emailbison/update_campaign.ts | 11 +- .../emailbison/update_campaign_status.ts | 11 +- apps/sim/tools/emailbison/update_lead.ts | 15 +- apps/sim/tools/emailbison/utils.ts | 45 +- apps/sim/tools/registry.ts | 8 +- apps/sim/tools/sharepoint/add_list_items.ts | 17 +- apps/sim/tools/sharepoint/create_list.ts | 46 +- apps/sim/tools/sharepoint/create_page.ts | 20 +- apps/sim/tools/sharepoint/get_list.ts | 92 +- apps/sim/tools/sharepoint/index.ts | 2 + apps/sim/tools/sharepoint/list_sites.ts | 44 +- apps/sim/tools/sharepoint/read_page.ts | 92 +- apps/sim/tools/sharepoint/types.ts | 50 +- apps/sim/tools/sharepoint/update_list.ts | 19 +- apps/sim/tools/sharepoint/upload_file.ts | 13 +- apps/sim/tools/sharepoint/utils.ts | 24 + .../emailbison/email_account_added.ts | 26 + .../emailbison/email_account_disconnected.ts | 26 + .../emailbison/email_account_reconnected.ts | 26 + .../emailbison/email_account_removed.ts | 26 + apps/sim/triggers/emailbison/email_bounced.ts | 26 + apps/sim/triggers/emailbison/email_opened.ts | 26 + apps/sim/triggers/emailbison/email_sent.ts | 27 + apps/sim/triggers/emailbison/index.ts | 17 + .../emailbison/lead_first_contacted.ts | 26 + .../triggers/emailbison/lead_interested.ts | 26 + apps/sim/triggers/emailbison/lead_replied.ts | 26 + .../triggers/emailbison/lead_unsubscribed.ts | 26 + .../triggers/emailbison/manual_email_sent.ts | 26 + apps/sim/triggers/emailbison/tag_attached.ts | 26 + apps/sim/triggers/emailbison/tag_removed.ts | 26 + .../emailbison/untracked_reply_received.ts | 26 + apps/sim/triggers/emailbison/utils.ts | 510 +++++++ .../warmup_disabled_causing_bounces.ts | 26 + .../warmup_disabled_receiving_bounces.ts | 26 + apps/sim/triggers/registry.ts | 36 + 61 files changed, 3423 insertions(+), 344 deletions(-) create mode 100644 apps/docs/content/docs/en/triggers/emailbison.mdx create mode 100644 apps/sim/lib/webhooks/providers/emailbison.ts create mode 100644 apps/sim/triggers/emailbison/email_account_added.ts create mode 100644 apps/sim/triggers/emailbison/email_account_disconnected.ts create mode 100644 apps/sim/triggers/emailbison/email_account_reconnected.ts create mode 100644 apps/sim/triggers/emailbison/email_account_removed.ts create mode 100644 apps/sim/triggers/emailbison/email_bounced.ts create mode 100644 apps/sim/triggers/emailbison/email_opened.ts create mode 100644 apps/sim/triggers/emailbison/email_sent.ts create mode 100644 apps/sim/triggers/emailbison/index.ts create mode 100644 apps/sim/triggers/emailbison/lead_first_contacted.ts create mode 100644 apps/sim/triggers/emailbison/lead_interested.ts create mode 100644 apps/sim/triggers/emailbison/lead_replied.ts create mode 100644 apps/sim/triggers/emailbison/lead_unsubscribed.ts create mode 100644 apps/sim/triggers/emailbison/manual_email_sent.ts create mode 100644 apps/sim/triggers/emailbison/tag_attached.ts create mode 100644 apps/sim/triggers/emailbison/tag_removed.ts create mode 100644 apps/sim/triggers/emailbison/untracked_reply_received.ts create mode 100644 apps/sim/triggers/emailbison/utils.ts create mode 100644 apps/sim/triggers/emailbison/warmup_disabled_causing_bounces.ts create mode 100644 apps/sim/triggers/emailbison/warmup_disabled_receiving_bounces.ts diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 38e82dd0c16..48748d79ba4 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -382,6 +382,7 @@ export const blockTypeToIconMap: Record = { ses: SESIcon, sftp: SftpIcon, sharepoint: MicrosoftSharepointIcon, + sharepoint_v2: MicrosoftSharepointIcon, shopify: ShopifyIcon, similarweb: SimilarwebIcon, sixtyfour: SixtyfourIcon, diff --git a/apps/docs/content/docs/en/tools/emailbison.mdx b/apps/docs/content/docs/en/tools/emailbison.mdx index 33fada79597..5e3b5d66369 100644 --- a/apps/docs/content/docs/en/tools/emailbison.mdx +++ b/apps/docs/content/docs/en/tools/emailbison.mdx @@ -26,7 +26,6 @@ Retrieves leads from Email Bison with optional search and tag filters. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `search` | string | No | Search term for filtering leads | | `campaignStatus` | string | No | Lead campaign status filter: in_sequence, sequence_finished, sequence_stopped, never_contacted, or replied | | `tagIds` | array | No | Tag IDs to include | @@ -62,7 +61,6 @@ Retrieves a lead by Email Bison lead ID or email address. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `leadId` | string | Yes | Lead ID or email address | #### Output @@ -92,7 +90,6 @@ Creates a single lead in Email Bison. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `firstName` | string | Yes | Lead first name | | `lastName` | string | Yes | Lead last name | | `email` | string | Yes | Lead email address | @@ -132,7 +129,6 @@ Updates an existing Email Bison lead. Fields omitted from a PUT update may be cl | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `leadId` | string | Yes | Lead ID or email address | | `firstName` | string | Yes | Lead first name | | `lastName` | string | Yes | Lead last name | @@ -173,7 +169,6 @@ Retrieves Email Bison campaigns. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | #### Output @@ -202,7 +197,6 @@ Creates a new Email Bison campaign. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `name` | string | Yes | Campaign name | | `campaignType` | string | No | Campaign type: outbound or reply_followup | @@ -233,7 +227,6 @@ Updates Email Bison campaign settings. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `campaignId` | number | Yes | Campaign ID | | `name` | string | No | Campaign name | | `maxEmailsPerDay` | number | No | Maximum emails per day | @@ -272,7 +265,6 @@ Pauses, resumes, or archives an Email Bison campaign. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `campaignId` | number | Yes | Campaign ID | | `action` | string | Yes | Status action: pause, resume, or archive | @@ -303,7 +295,6 @@ Adds existing Email Bison leads to a campaign. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `campaignId` | number | Yes | Campaign ID | | `leadIds` | array | Yes | Lead IDs to add to the campaign | | `items` | number | No | No description | @@ -336,7 +327,6 @@ Retrieves Email Bison replies with optional status, folder, campaign, sender, le | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `search` | string | No | Search term for replies | | `status` | string | No | Reply status: interested, automated_reply, or not_automated_reply | | `folder` | string | No | Reply folder: inbox, sent, spam, bounced, or all | @@ -374,7 +364,6 @@ Retrieves all Email Bison tags for the authenticated workspace. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | #### Output @@ -403,7 +392,6 @@ Creates a new Email Bison tag. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `name` | string | Yes | Tag name | #### Output @@ -433,7 +421,6 @@ Attaches Email Bison tags to one or more leads. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `apiKey` | string | Yes | Email Bison API token | | `tagIds` | array | Yes | Tag IDs to attach | | `items` | number | No | No description | | `leadIds` | array | Yes | Lead IDs to tag | diff --git a/apps/docs/content/docs/en/tools/sharepoint.mdx b/apps/docs/content/docs/en/tools/sharepoint.mdx index 13027ea8261..cf83987bd40 100644 --- a/apps/docs/content/docs/en/tools/sharepoint.mdx +++ b/apps/docs/content/docs/en/tools/sharepoint.mdx @@ -1,12 +1,12 @@ --- -title: Sharepoint +title: SharePoint description: Work with pages and lists --- import { BlockInfoCard } from "@/components/ui/block-info-card" @@ -73,6 +73,7 @@ Read a specific page from a SharePoint site | `pageId` | string | No | The ID of the page to read. Example: a GUID like 12345678-1234-1234-1234-123456789012 | | `pageName` | string | No | The name of the page to read \(alternative to pageId\). Example: Home.aspx or About-Us.aspx | | `maxPages` | number | No | Maximum number of pages to return when listing all pages \(default: 10, max: 50\) | +| `nextPageUrl` | string | No | Full @odata.nextLink URL from a previous Microsoft Graph page response | #### Output @@ -84,6 +85,7 @@ Read a specific page from a SharePoint site | ↳ `title` | string | The title of the page | | ↳ `webUrl` | string | The URL to access the page | | ↳ `pageLayout` | string | The layout type of the page | +| ↳ `description` | string | The description of the page | | ↳ `createdDateTime` | string | When the page was created | | ↳ `lastModifiedDateTime` | string | When the page was last modified | | `pages` | array | List of SharePoint pages | @@ -93,6 +95,7 @@ Read a specific page from a SharePoint site | ↳ `title` | string | The title of the page | | ↳ `webUrl` | string | The URL to access the page | | ↳ `pageLayout` | string | The layout type of the page | +| ↳ `description` | string | The description of the page | | ↳ `createdDateTime` | string | When the page was created | | ↳ `lastModifiedDateTime` | string | When the page was last modified | | ↳ `content` | object | content output from the tool | @@ -102,6 +105,7 @@ Read a specific page from a SharePoint site | ↳ `content` | string | Extracted text content from the page | | ↳ `canvasLayout` | object | Raw SharePoint canvas layout structure | | `totalPages` | number | Total number of pages found | +| `nextPageUrl` | string | Full Microsoft Graph @odata.nextLink URL for the next page of results | ### `sharepoint_list_sites` @@ -112,7 +116,9 @@ List details of all SharePoint sites | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `siteSelector` | string | No | Select the SharePoint site | +| `siteId` | string | No | The ID of the SharePoint site \(internal use\) | | `groupId` | string | No | The group ID for accessing a group team site. Example: a GUID like 12345678-1234-1234-1234-123456789012 | +| `nextPageUrl` | string | No | Full @odata.nextLink URL from a previous Microsoft Graph page response | #### Output @@ -139,6 +145,7 @@ List details of all SharePoint sites | ↳ `description` | string | The description of the site | | ↳ `createdDateTime` | string | When the site was created | | ↳ `lastModifiedDateTime` | string | When the site was last modified | +| `nextPageUrl` | string | Full Microsoft Graph @odata.nextLink URL for the next page of results | ### `sharepoint_create_list` @@ -153,7 +160,7 @@ Create a new list in a SharePoint site | `listDisplayName` | string | Yes | Display name of the list to create. Example: Project Tasks or Customer Contacts | | `listDescription` | string | No | Description of the list | | `listTemplate` | string | No | List template name \(e.g., 'genericList'\) | -| `pageContent` | string | No | Optional JSON of columns. Either a top-level array of column definitions or an object with \{ columns: \[...\] \}. | +| `pageContent` | json | No | Optional JSON of columns. Either a top-level array of column definitions or an object with \{ columns: \[...\] \}. | #### Output @@ -179,6 +186,9 @@ Get metadata (and optionally columns/items) for a SharePoint list | `siteSelector` | string | No | Select the SharePoint site | | `siteId` | string | No | The ID of the SharePoint site \(internal use\) | | `listId` | string | No | The ID of the list to retrieve. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012 | +| `includeColumns` | boolean | No | Whether to include column definitions when retrieving a specific list | +| `includeItems` | boolean | No | Whether to include list items when retrieving a specific list | +| `nextPageUrl` | string | No | Full @odata.nextLink URL from a previous Microsoft Graph page response | #### Output @@ -194,6 +204,10 @@ Get metadata (and optionally columns/items) for a SharePoint list | ↳ `list` | object | List properties \(e.g., template\) | | ↳ `columns` | array | List column definitions | | `lists` | array | All lists in the site when no listId/title provided | +| `items` | array | List items with expanded fields when reading list items | +| ↳ `id` | string | Item ID | +| ↳ `fields` | object | Field values for the item | +| `nextPageUrl` | string | Full Microsoft Graph @odata.nextLink URL for the next page of results | ### `sharepoint_update_list` @@ -205,9 +219,9 @@ Update the properties (fields) on a SharePoint list item | --------- | ---- | -------- | ----------- | | `siteSelector` | string | No | Select the SharePoint site | | `siteId` | string | No | The ID of the SharePoint site \(internal use\) | -| `listId` | string | No | The ID of the list containing the item. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012 | +| `listId` | string | Yes | The ID of the list containing the item. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012 | | `itemId` | string | Yes | The ID of the list item to update. Example: 1, 42, or 123 | -| `listItemFields` | object | Yes | Field values to update on the list item | +| `listItemFields` | json | Yes | Field values to update on the list item | #### Output @@ -228,7 +242,7 @@ Add a new item to a SharePoint list | `siteSelector` | string | No | Select the SharePoint site | | `siteId` | string | No | The ID of the SharePoint site \(internal use\) | | `listId` | string | Yes | The ID of the list to add the item to. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012 | -| `listItemFields` | object | Yes | Field values for the new list item | +| `listItemFields` | json | Yes | Field values for the new list item | #### Output @@ -250,7 +264,7 @@ Upload files to a SharePoint document library | `driveId` | string | No | The ID of the document library \(drive\). If not provided, uses default drive. Example: b!abc123def456 | | `folderPath` | string | No | Optional folder path within the document library. Example: /Documents/Subfolder or /Shared Documents/Reports | | `fileName` | string | No | Optional: override the uploaded file name. Example: report-2024.pdf | -| `files` | file[] | No | Files to upload to SharePoint | +| `files` | file[] | Yes | Files to upload to SharePoint | #### Output diff --git a/apps/docs/content/docs/en/triggers/emailbison.mdx b/apps/docs/content/docs/en/triggers/emailbison.mdx new file mode 100644 index 00000000000..94a8c041659 --- /dev/null +++ b/apps/docs/content/docs/en/triggers/emailbison.mdx @@ -0,0 +1,1178 @@ +--- +title: Emailbison +description: Available Emailbison triggers for automating workflows +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +Emailbison provides 17 triggers for automating workflows based on events. + +## Triggers + +### Email Bison Contact First Emailed + +Trigger when a contact receives their first campaign email in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `lead_id` | number | Lead ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `email_subject` | string | Email subject | +| ↳ `email_body` | string | Email body HTML | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Contact Interested + +Trigger when a reply is marked interested in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `uuid` | string | Reply UUID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | number | Parent reply ID | +| ↳ `reply_type` | string | Reply type | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Contact Replied + +Trigger when a campaign lead replies in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `uuid` | string | Reply UUID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | number | Parent reply ID | +| ↳ `reply_type` | string | Reply type | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Contact Unsubscribed + +Trigger when a contact unsubscribes in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `lead_id` | number | Lead ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `email_subject` | string | Email subject | +| ↳ `email_body` | string | Email body HTML | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Account Added + +Trigger when a sender email account is added to Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Account Disconnected + +Trigger when a sender email account disconnects in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Account Reconnected + +Trigger when a sender email account reconnects in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Account Removed + +Trigger when a sender email account is removed from Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Bounced + +Trigger when an Email Bison campaign email bounces + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `uuid` | string | Reply UUID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | number | Parent reply ID | +| ↳ `reply_type` | string | Reply type | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Opened + +Trigger when an Email Bison campaign email is opened + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `lead_id` | number | Lead ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `email_subject` | string | Email subject | +| ↳ `email_body` | string | Email body HTML | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Sent + +Trigger when a campaign email is sent in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `lead_id` | number | Lead ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `email_subject` | string | Email subject | +| ↳ `email_body` | string | Email body HTML | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Manual Email Sent + +Trigger when a manual email is sent in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `reply_type` | string | Reply type | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | json | Parent reply ID | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | json | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Tag Attached + +Trigger when a custom tag is attached to a taggable in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `tagId` | number | Email Bison tag ID | +| `tagName` | string | Email Bison tag name | +| `taggableId` | number | ID of the tagged resource | +| `taggableType` | string | Type of the tagged resource | + + +--- + +### Email Bison Tag Removed + +Trigger when a custom tag is removed from a taggable in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `tagId` | number | Email Bison tag ID | +| `tagName` | string | Email Bison tag name | +| `taggableId` | number | ID of the tagged resource | +| `taggableType` | string | Type of the tagged resource | + + +--- + +### Email Bison Untracked Reply Received + +Trigger when Email Bison receives a reply not tied to a scheduled campaign email + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `uuid` | string | Reply UUID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | number | Parent reply ID | +| ↳ `reply_type` | string | Reply type | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Warmup Disabled Causing Bounces + +Trigger when warmup is disabled for a sender email causing too many bounces + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Warmup Disabled Receiving Bounces + +Trigger when warmup is disabled for a sender email receiving too many bounces + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + diff --git a/apps/docs/content/docs/en/triggers/meta.json b/apps/docs/content/docs/en/triggers/meta.json index 7eff814aad7..70d13afd920 100644 --- a/apps/docs/content/docs/en/triggers/meta.json +++ b/apps/docs/content/docs/en/triggers/meta.json @@ -12,6 +12,7 @@ "calendly", "circleback", "confluence", + "emailbison", "fathom", "fireflies", "github", diff --git a/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx b/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx index 51a6dfa72fe..eb700c9708b 100644 --- a/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx +++ b/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx @@ -75,12 +75,21 @@ const AUTH_STEP: Record = { none: 'Authenticate your account to connect.', } +/** + * Ensures autogenerated prose can be safely composed with a following sentence. + */ +function sentenceWithTerminalPunctuation(value: string): string { + const trimmedValue = value.trim() + return /[.!?]$/.test(trimmedValue) ? trimmedValue : `${trimmedValue}.` +} + /** * Generates targeted FAQs from integration metadata. * Questions mirror real search queries to drive FAQPage rich snippets. */ function buildFAQs(integration: Integration): FAQItem[] { const { name, description, operations, triggers, authType } = integration + const faqDescription = sentenceWithTerminalPunctuation(description) const topOps = operations.slice(0, 5) const topOpNames = topOps.map((o) => o.name) const authStep = AUTH_STEP[authType] @@ -88,7 +97,7 @@ function buildFAQs(integration: Integration): FAQItem[] { const faqs: FAQItem[] = [ { question: `What is Sim's ${name} integration?`, - answer: `Sim's ${name} integration lets you build AI agents that automate tasks in ${name} without writing code. ${description} You can connect ${name} to hundreds of other services in the same agent — from CRMs and spreadsheets to messaging tools and databases.`, + answer: `Sim's ${name} integration lets you build AI agents that automate tasks in ${name} without writing code. ${faqDescription} You can connect ${name} to hundreds of other services in the same agent — from CRMs and spreadsheets to messaging tools and databases.`, }, { question: `What can I automate with ${name} in Sim?`, diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index 96adf9a1b62..d1dc93e0a96 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -363,7 +363,7 @@ export const blockTypeToIconMap: Record = { servicenow: ServiceNowIcon, ses: SESIcon, sftp: SftpIcon, - sharepoint: MicrosoftSharepointIcon, + sharepoint_v2: MicrosoftSharepointIcon, shopify: ShopifyIcon, similarweb: SimilarwebIcon, sixtyfour: SixtyfourIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 43e8e52db1c..597634c1611 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -11940,9 +11940,9 @@ "tags": ["cloud", "automation"] }, { - "type": "sharepoint", + "type": "sharepoint_v2", "slug": "sharepoint", - "name": "Sharepoint", + "name": "SharePoint", "description": "Work with pages and lists", "longDescription": "Integrate SharePoint into the workflow. Read/create pages, list sites, and work with lists (read, create, update items). Requires OAuth.", "bgColor": "#E0E0E0", @@ -11970,11 +11970,11 @@ "description": "Get metadata (and optionally columns/items) for a SharePoint list" }, { - "name": "Update List", + "name": "Update List Item", "description": "Update the properties (fields) on a SharePoint list item" }, { - "name": "Add List Items", + "name": "Add List Item", "description": "Add a new item to a SharePoint list" }, { diff --git a/apps/sim/app/api/tools/sharepoint/upload/route.ts b/apps/sim/app/api/tools/sharepoint/upload/route.ts index 20a735a1de9..429a8a24d08 100644 --- a/apps/sim/app/api/tools/sharepoint/upload/route.ts +++ b/apps/sim/app/api/tools/sharepoint/upload/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { sharepointUploadContract } from '@/lib/api/contracts/tools/microsoft' import { parseRequest } from '@/lib/api/server' @@ -72,42 +73,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - let effectiveDriveId = validatedData.driveId - if (!effectiveDriveId) { - logger.info(`[${requestId}] No driveId provided, fetching default drive for site`) - const driveUrl = `https://graph.microsoft.com/v1.0/sites/${validatedData.siteId}/drive` - const driveResponse = await secureFetchWithValidation( - driveUrl, - { - method: 'GET', - headers: { - Authorization: `Bearer ${validatedData.accessToken}`, - Accept: 'application/json', - }, - }, - 'driveUrl' - ) - - if (!driveResponse.ok) { - const errorData = (await driveResponse.json().catch(() => ({}))) as { - error?: { message?: string } - } - logger.error(`[${requestId}] Failed to get default drive:`, errorData) - return NextResponse.json( - { - success: false, - error: errorData.error?.message || 'Failed to get default document library', - }, - { status: driveResponse.status } - ) - } - - const driveData = (await driveResponse.json()) as { id: string } - effectiveDriveId = driveData.id - logger.info(`[${requestId}] Using default drive: ${effectiveDriveId}`) - } - - const uploadedFiles: any[] = [] + const siteId = validatedData.siteId.trim() || 'root' + const driveId = validatedData.driveId?.trim() || null + const uploadedFiles: MicrosoftGraphDriveItem[] = [] for (const userFile of userFiles) { logger.info(`[${requestId}] Uploading file: ${userFile.name}`) @@ -142,7 +110,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { .map((segment) => (segment ? encodeURIComponent(segment) : '')) .join('/') - const uploadUrl = `https://graph.microsoft.com/v1.0/sites/${validatedData.siteId}/drives/${effectiveDriveId}/root:${encodedPath}:/content` + const uploadUrl = driveId + ? `https://graph.microsoft.com/v1.0/drives/${driveId}/root:${encodedPath}:/content` + : `https://graph.microsoft.com/v1.0/sites/${siteId}/drive/root:${encodedPath}:/content` logger.info(`[${requestId}] Uploading to: ${uploadUrl}`) @@ -263,7 +233,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json( { success: false, - error: error instanceof Error ? error.message : 'Unknown error occurred', + error: toError(error).message, }, { status: 500 } ) diff --git a/apps/sim/blocks/blocks/emailbison.ts b/apps/sim/blocks/blocks/emailbison.ts index 48d5c0dfb18..b253703b16b 100644 --- a/apps/sim/blocks/blocks/emailbison.ts +++ b/apps/sim/blocks/blocks/emailbison.ts @@ -2,6 +2,7 @@ import { EmailBisonIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' import type { EmailBisonResponse } from '@/tools/emailbison/types' +import { getTrigger } from '@/triggers' const LEAD_MUTATION_OPERATIONS = ['create_lead', 'update_lead'] as const const LEAD_LIST_OPERATIONS = ['list_leads'] as const @@ -12,6 +13,25 @@ const CAMPAIGN_ID_OPERATIONS = [ ] as const const REPLY_FILTER_OPERATIONS = ['list_replies'] as const const TAG_FILTER_OPERATIONS = ['list_leads', 'list_replies'] as const +const EMAILBISON_TRIGGER_IDS = [ + 'emailbison_email_sent', + 'emailbison_lead_first_contacted', + 'emailbison_lead_replied', + 'emailbison_lead_interested', + 'emailbison_lead_unsubscribed', + 'emailbison_untracked_reply_received', + 'emailbison_email_opened', + 'emailbison_email_bounced', + 'emailbison_email_account_added', + 'emailbison_email_account_removed', + 'emailbison_email_account_disconnected', + 'emailbison_email_account_reconnected', + 'emailbison_manual_email_sent', + 'emailbison_tag_attached', + 'emailbison_tag_removed', + 'emailbison_warmup_disabled_receiving_bounces', + 'emailbison_warmup_disabled_causing_bounces', +] as const export const EmailBisonBlock: BlockConfig = { type: 'emailbison', @@ -26,6 +46,10 @@ export const EmailBisonBlock: BlockConfig = { bgColor: '#FB7A22', icon: EmailBisonIcon, authMode: AuthMode.ApiKey, + triggers: { + enabled: true, + available: [...EMAILBISON_TRIGGER_IDS], + }, subBlocks: [ { id: 'operation', @@ -56,6 +80,13 @@ export const EmailBisonBlock: BlockConfig = { placeholder: 'Enter your Email Bison API token', required: true, }, + { + id: 'apiBaseUrl', + title: 'Instance URL', + type: 'short-input', + placeholder: 'https://your-emailbison-workspace.com', + required: true, + }, { id: 'leadId', title: 'Lead ID or Email', @@ -428,6 +459,7 @@ export const EmailBisonBlock: BlockConfig = { condition: { field: 'operation', value: 'attach_tags_to_leads' }, mode: 'advanced', }, + ...EMAILBISON_TRIGGER_IDS.flatMap((triggerId) => getTrigger(triggerId).subBlocks), ], tools: { access: [ @@ -448,6 +480,7 @@ export const EmailBisonBlock: BlockConfig = { config: { tool: (params) => `emailbison_${params.operation}`, params: (params) => ({ + apiBaseUrl: params.apiBaseUrl, leadId: params.operation === 'list_replies' ? toNumberParam(params.replyLeadId) : params.leadId, leadIds: parseNumberList(params.leadIds), @@ -487,6 +520,7 @@ export const EmailBisonBlock: BlockConfig = { inputs: { operation: { type: 'string', description: 'Operation to perform' }, apiKey: { type: 'string', description: 'Email Bison API token' }, + apiBaseUrl: { type: 'string', description: 'Email Bison instance URL' }, leadId: { type: 'string', description: 'Lead ID or email address' }, search: { type: 'string', description: 'Search term' }, campaignStatus: { type: 'string', description: 'Lead campaign status filter' }, diff --git a/apps/sim/blocks/blocks/sharepoint.ts b/apps/sim/blocks/blocks/sharepoint.ts index 5698d544d85..fe3879920eb 100644 --- a/apps/sim/blocks/blocks/sharepoint.ts +++ b/apps/sim/blocks/blocks/sharepoint.ts @@ -14,6 +14,7 @@ export const SharepointBlock: BlockConfig = { name: 'Sharepoint', description: 'Work with pages and lists', authMode: AuthMode.OAuth, + hideFromToolbar: true, longDescription: 'Integrate SharePoint into the workflow. Read/create pages, list sites, and work with lists (read, create, update items). Requires OAuth.', docsLink: 'https://docs.sim.ai/tools/sharepoint', @@ -567,3 +568,478 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, }, }, } + +const SHAREPOINT_V2_TOOL_IDS = [ + 'sharepoint_create_page', + 'sharepoint_read_page', + 'sharepoint_list_sites', + 'sharepoint_create_list', + 'sharepoint_get_list', + 'sharepoint_update_list', + 'sharepoint_add_list_items', + 'sharepoint_upload_file', +] as const + +const SHAREPOINT_V2_SITE_OPERATIONS = Array.from(SHAREPOINT_V2_TOOL_IDS) + +const SHAREPOINT_V2_LIST_ITEM_OPERATIONS = [ + 'sharepoint_update_list', + 'sharepoint_add_list_items', +] as const + +export const SharepointV2Block: BlockConfig = { + ...SharepointBlock, + type: 'sharepoint_v2', + name: 'SharePoint', + hideFromToolbar: false, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Create Page', id: 'sharepoint_create_page' }, + { label: 'Read Page', id: 'sharepoint_read_page' }, + { label: 'List Sites', id: 'sharepoint_list_sites' }, + { label: 'Create List', id: 'sharepoint_create_list' }, + { label: 'Read List', id: 'sharepoint_get_list' }, + { label: 'Update List Item', id: 'sharepoint_update_list' }, + { label: 'Add List Item', id: 'sharepoint_add_list_items' }, + { label: 'Upload File', id: 'sharepoint_upload_file' }, + ], + value: () => 'sharepoint_create_page', + }, + { + id: 'credential', + title: 'Microsoft Account', + type: 'oauth-input', + canonicalParamId: 'oauthCredential', + mode: 'basic', + serviceId: 'sharepoint', + requiredScopes: getScopesForService('sharepoint'), + placeholder: 'Select Microsoft account', + required: true, + }, + { + id: 'manualCredential', + title: 'Microsoft Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + { + id: 'siteSelector', + title: 'Select Site', + type: 'file-selector', + canonicalParamId: 'siteId', + serviceId: 'sharepoint', + selectorKey: 'sharepoint.sites', + requiredScopes: getScopesForService('sharepoint'), + mimeType: 'application/vnd.microsoft.graph.site', + placeholder: 'Select a site', + dependsOn: ['credential'], + mode: 'basic', + condition: { field: 'operation', value: SHAREPOINT_V2_SITE_OPERATIONS }, + }, + { + id: 'manualSiteId', + title: 'Site ID', + type: 'short-input', + canonicalParamId: 'siteId', + placeholder: 'Enter site ID (leave empty for root site)', + dependsOn: ['credential'], + mode: 'advanced', + condition: { field: 'operation', value: SHAREPOINT_V2_SITE_OPERATIONS }, + }, + { + id: 'pageName', + title: 'Page Name', + type: 'short-input', + placeholder: 'Name of the page', + condition: { field: 'operation', value: ['sharepoint_create_page', 'sharepoint_read_page'] }, + required: { field: 'operation', value: 'sharepoint_create_page' }, + }, + { + id: 'pageId', + title: 'Page ID', + type: 'short-input', + placeholder: 'Page ID (alternative to page name)', + condition: { field: 'operation', value: 'sharepoint_read_page' }, + mode: 'advanced', + }, + { + id: 'pageTitle', + title: 'Page Title', + type: 'short-input', + placeholder: 'Optional title (defaults to page name)', + condition: { field: 'operation', value: 'sharepoint_create_page' }, + mode: 'advanced', + }, + { + id: 'pageContent', + title: 'Page Content', + type: 'long-input', + placeholder: 'Optional text content for the page', + condition: { field: 'operation', value: 'sharepoint_create_page' }, + mode: 'advanced', + }, + { + id: 'maxPages', + title: 'Max Pages', + type: 'short-input', + placeholder: 'Default 10, maximum 50', + condition: { field: 'operation', value: 'sharepoint_read_page' }, + mode: 'advanced', + }, + { + id: 'groupId', + title: 'Group ID', + type: 'short-input', + placeholder: 'Optional Microsoft 365 group ID', + condition: { field: 'operation', value: 'sharepoint_list_sites' }, + mode: 'advanced', + }, + { + id: 'listSelector', + title: 'List', + type: 'file-selector', + canonicalParamId: 'listId', + serviceId: 'sharepoint', + selectorKey: 'sharepoint.lists', + placeholder: 'Select a list', + dependsOn: ['credential', 'siteSelector'], + mode: 'basic', + condition: { + field: 'operation', + value: ['sharepoint_get_list', ...SHAREPOINT_V2_LIST_ITEM_OPERATIONS], + }, + required: { field: 'operation', value: [...SHAREPOINT_V2_LIST_ITEM_OPERATIONS] }, + }, + { + id: 'manualListId', + title: 'List ID', + type: 'short-input', + canonicalParamId: 'listId', + placeholder: 'Enter list ID (GUID). Required for list item operations.', + mode: 'advanced', + condition: { + field: 'operation', + value: ['sharepoint_get_list', ...SHAREPOINT_V2_LIST_ITEM_OPERATIONS], + }, + required: { field: 'operation', value: [...SHAREPOINT_V2_LIST_ITEM_OPERATIONS] }, + }, + { + id: 'includeColumns', + title: 'Include Columns', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + value: () => 'false', + condition: { field: 'operation', value: 'sharepoint_get_list' }, + mode: 'advanced', + }, + { + id: 'includeItems', + title: 'Include Items', + type: 'dropdown', + options: [ + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'true', + condition: { field: 'operation', value: 'sharepoint_get_list' }, + mode: 'advanced', + }, + { + id: 'listDisplayName', + title: 'List Display Name', + type: 'short-input', + placeholder: 'Name of the list', + condition: { field: 'operation', value: 'sharepoint_create_list' }, + required: { field: 'operation', value: 'sharepoint_create_list' }, + }, + { + id: 'listTemplate', + title: 'List Template', + type: 'short-input', + placeholder: "Template (e.g., 'genericList')", + condition: { field: 'operation', value: 'sharepoint_create_list' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a SharePoint list template name based on the user's description. + +### AVAILABLE TEMPLATES +- genericList - Standard list for general data (default) +- documentLibrary - For storing and managing documents +- survey - For creating surveys and polls +- links - For storing hyperlinks +- announcements - For news and announcements +- contacts - For contact information (name, email, phone) +- events - For calendar events and scheduling +- tasks - For task tracking and project management +- discussionBoard - For team discussions and forums +- pictureLibrary - For storing images and photos +- issue - For issue/bug tracking + +Return ONLY the template name - no explanations, no quotes, no extra text.`, + placeholder: 'Describe what kind of list you need...', + }, + }, + { + id: 'columnDefinitions', + title: 'Column Definitions', + type: 'long-input', + placeholder: 'Optional: Define custom columns as JSON array', + condition: { field: 'operation', value: 'sharepoint_create_list' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: `Generate a JSON array of SharePoint list column definitions based on the user's description. + +Each column needs at minimum a "name" and column type properties, for example: +[{"name": "Status", "choice": {"choices": ["Active", "Completed"]}}, {"name": "DueDate", "dateTime": {"format": "dateOnly"}}] + +Return ONLY the JSON array - no explanations, no markdown, no extra text.`, + placeholder: 'Describe the columns you want to add...', + generationType: 'json-object', + }, + }, + { + id: 'listDescription', + title: 'List Description', + type: 'long-input', + placeholder: 'Optional description', + condition: { field: 'operation', value: 'sharepoint_create_list' }, + mode: 'advanced', + }, + { + id: 'itemId', + title: 'Item ID', + type: 'short-input', + placeholder: 'Enter item ID', + condition: { field: 'operation', value: 'sharepoint_update_list' }, + required: { field: 'operation', value: 'sharepoint_update_list' }, + }, + { + id: 'listItemFields', + title: 'List Item Fields', + type: 'long-input', + placeholder: + 'Enter list item fields as JSON (e.g., {"Title": "My Item", "Status": "Active"})', + condition: { field: 'operation', value: [...SHAREPOINT_V2_LIST_ITEM_OPERATIONS] }, + required: { field: 'operation', value: [...SHAREPOINT_V2_LIST_ITEM_OPERATIONS] }, + wandConfig: { + enabled: true, + prompt: `Generate a JSON object for SharePoint list item fields based on the user's description. + +Use column internal names as keys and valid field values as values. + +Return ONLY the JSON object - no explanations, no markdown, no extra text.`, + placeholder: 'Describe the fields and values you want to set...', + generationType: 'json-object', + }, + }, + { + id: 'driveId', + title: 'Document Library ID', + type: 'short-input', + placeholder: 'Enter document library (drive) ID', + condition: { field: 'operation', value: 'sharepoint_upload_file' }, + mode: 'advanced', + }, + { + id: 'folderPath', + title: 'Folder Path', + type: 'short-input', + placeholder: 'Optional folder path (e.g., /Documents/Subfolder)', + condition: { field: 'operation', value: 'sharepoint_upload_file' }, + mode: 'advanced', + required: false, + }, + { + id: 'fileName', + title: 'File Name', + type: 'short-input', + placeholder: 'Optional: override uploaded file name', + condition: { field: 'operation', value: 'sharepoint_upload_file' }, + mode: 'advanced', + required: false, + }, + { + id: 'uploadFiles', + title: 'Files', + type: 'file-upload', + canonicalParamId: 'files', + placeholder: 'Upload files to SharePoint', + condition: { field: 'operation', value: 'sharepoint_upload_file' }, + mode: 'basic', + multiple: true, + required: true, + }, + { + id: 'fileRefs', + title: 'Files', + type: 'short-input', + canonicalParamId: 'files', + placeholder: 'Reference files from previous blocks', + condition: { field: 'operation', value: 'sharepoint_upload_file' }, + mode: 'advanced', + required: true, + }, + { + id: 'nextPageUrl', + title: 'Next Page URL', + type: 'short-input', + placeholder: 'Paste the @odata.nextLink URL from a previous result', + condition: { + field: 'operation', + value: ['sharepoint_read_page', 'sharepoint_list_sites', 'sharepoint_get_list'], + }, + mode: 'advanced', + }, + ], + tools: { + access: [...SHAREPOINT_V2_TOOL_IDS], + config: { + tool: (params) => params.operation || 'sharepoint_create_page', + params: (params) => { + const { + oauthCredential, + siteId, + listId, + itemId, + includeColumns, + includeItems, + columnDefinitions, + listItemFields, + files, + uploadFiles, + fileRefs, + maxPages, + driveId, + ...rest + } = params + + const cleanString = (value: unknown) => + value === undefined || value === null ? undefined : String(value).trim() || undefined + const coerceBoolean = (value: unknown) => { + if (typeof value === 'boolean') return value + if (typeof value === 'string') return value.toLowerCase() === 'true' + return undefined + } + const parseJsonObject = (value: unknown) => { + if (typeof value !== 'string') return value + if (!value.trim()) return undefined + try { + return JSON.parse(value) + } catch (error) { + logger.error('Failed to parse SharePoint JSON input', { + error: toError(error).message, + }) + return undefined + } + } + + const normalizedFiles = normalizeFileInput(files || uploadFiles || fileRefs) + const result: Record = { + ...rest, + oauthCredential, + siteId: cleanString(siteId), + listId: cleanString(listId), + itemId: cleanString(itemId), + driveId: cleanString(driveId), + includeColumns: coerceBoolean(includeColumns), + includeItems: coerceBoolean(includeItems), + listItemFields: parseJsonObject(listItemFields), + maxPages: maxPages ? Number.parseInt(String(maxPages), 10) : undefined, + } + + if (columnDefinitions) { + result.pageContent = columnDefinitions + } + if (normalizedFiles) { + result.files = normalizedFiles + } + + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + oauthCredential: { type: 'string', description: 'Microsoft account credential' }, + siteId: { type: 'string', description: 'SharePoint site ID' }, + pageName: { type: 'string', description: 'Page name' }, + pageTitle: { type: 'string', description: 'Page title' }, + pageContent: { type: 'string', description: 'Page text content' }, + pageId: { type: 'string', description: 'Page ID' }, + maxPages: { type: 'number', description: 'Maximum pages to return' }, + nextPageUrl: { type: 'string', description: 'Microsoft Graph @odata.nextLink URL' }, + groupId: { type: 'string', description: 'Microsoft 365 group ID' }, + listId: { type: 'string', description: 'List ID' }, + includeColumns: { type: 'boolean', description: 'Include columns in response' }, + includeItems: { type: 'boolean', description: 'Include items in response' }, + listDisplayName: { type: 'string', description: 'List display name' }, + listDescription: { type: 'string', description: 'List description' }, + listTemplate: { type: 'string', description: 'List template' }, + columnDefinitions: { + type: 'json', + description: 'Column definitions for list creation (JSON array)', + }, + itemId: { type: 'string', description: 'List item ID' }, + listItemFields: { type: 'json', description: 'List item fields' }, + driveId: { type: 'string', description: 'Document library (drive) ID' }, + folderPath: { type: 'string', description: 'Folder path for file upload' }, + fileName: { type: 'string', description: 'File name override' }, + files: { type: 'json', description: 'Files to upload' }, + }, + outputs: { + site: { + type: 'json', + description: 'SharePoint site object (id, name, displayName, webUrl, description)', + }, + sites: { + type: 'json', + description: 'Array of SharePoint site objects (id, name, displayName, webUrl)', + }, + page: { + type: 'json', + description: 'SharePoint page object (id, name, title, webUrl, pageLayout, description)', + }, + pages: { + type: 'json', + description: 'Array of SharePoint pages with page metadata and extracted content', + }, + content: { + type: 'json', + description: 'SharePoint page content (content, canvasLayout)', + }, + totalPages: { type: 'number', description: 'Number of pages returned' }, + list: { + type: 'json', + description: 'SharePoint list object (id, displayName, name, webUrl, columns, items)', + }, + lists: { + type: 'json', + description: 'Array of SharePoint lists (id, displayName, name, webUrl, list)', + }, + item: { type: 'json', description: 'SharePoint list item with fields' }, + items: { type: 'json', description: 'Array of SharePoint list items with fields' }, + uploadedFiles: { + type: 'json', + description: 'Array of uploaded file objects with id, name, webUrl, size', + }, + fileCount: { type: 'number', description: 'Number of files uploaded' }, + nextPageUrl: { + type: 'string', + description: 'Microsoft Graph @odata.nextLink URL for the next page of results', + }, + success: { type: 'boolean', description: 'Success status' }, + error: { type: 'string', description: 'Error message' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 5a8e162fe6f..54b2312c086 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -181,7 +181,7 @@ import { SerperBlock } from '@/blocks/blocks/serper' import { ServiceNowBlock } from '@/blocks/blocks/servicenow' import { SESBlock } from '@/blocks/blocks/ses' import { SftpBlock } from '@/blocks/blocks/sftp' -import { SharepointBlock } from '@/blocks/blocks/sharepoint' +import { SharepointBlock, SharepointV2Block } from '@/blocks/blocks/sharepoint' import { ShopifyBlock } from '@/blocks/blocks/shopify' import { SimilarwebBlock } from '@/blocks/blocks/similarweb' import { SixtyfourBlock } from '@/blocks/blocks/sixtyfour' @@ -433,6 +433,7 @@ export const registry: Record = { servicenow: ServiceNowBlock, sftp: SftpBlock, sharepoint: SharepointBlock, + sharepoint_v2: SharepointV2Block, shopify: ShopifyBlock, similarweb: SimilarwebBlock, sixtyfour: SixtyfourBlock, diff --git a/apps/sim/lib/webhooks/providers/emailbison.ts b/apps/sim/lib/webhooks/providers/emailbison.ts new file mode 100644 index 00000000000..116e9f7337c --- /dev/null +++ b/apps/sim/lib/webhooks/providers/emailbison.ts @@ -0,0 +1,300 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils' +import type { + DeleteSubscriptionContext, + EventMatchContext, + FormatInputContext, + FormatInputResult, + SubscriptionContext, + SubscriptionResult, + WebhookProviderHandler, +} from '@/lib/webhooks/providers/types' +import { emailBisonHeaders, emailBisonUrl } from '@/tools/emailbison/utils' + +const logger = createLogger('WebhookProvider:EmailBison') + +export const emailBisonHandler: WebhookProviderHandler = { + async matchEvent({ body, providerConfig, requestId }: EventMatchContext): Promise { + const triggerId = providerConfig.triggerId as string | undefined + if (!triggerId) return true + + if (!isRecord(body)) { + logger.warn(`[${requestId}] Email Bison webhook payload was not an object`) + return false + } + + const { isEmailBisonEventMatch } = await import('@/triggers/emailbison/utils') + if (!isEmailBisonEventMatch(triggerId, unwrapEmailBisonPayload(body))) { + logger.info(`[${requestId}] Email Bison event did not match trigger`, { + triggerId, + }) + return false + } + + return true + }, + + async formatInput({ body, webhook }: FormatInputContext): Promise { + const payload = isRecord(body) ? unwrapEmailBisonPayload(body) : {} + const event = isRecord(payload.event) ? payload.event : null + const data = isRecord(payload.data) ? payload.data : null + const providerConfig = getProviderConfig(webhook) + const triggerId = providerConfig.triggerId as string | undefined + const input: Record = { + eventType: toStringOrNull(event?.type), + eventName: toStringOrNull(event?.name), + instanceUrl: toStringOrNull(event?.instance_url), + workspaceId: toNumberOrNull(event?.workspace_id), + workspaceName: toStringOrNull(event?.workspace_name), + event, + data, + } + + if ( + triggerId === 'emailbison_email_sent' || + triggerId === 'emailbison_lead_first_contacted' || + triggerId === 'emailbison_lead_unsubscribed' || + triggerId === 'emailbison_email_opened' + ) { + input.scheduledEmail = toRecordOrNull(data?.scheduled_email) + input.campaignEvent = renameTypeField(data?.campaign_event, 'event_type') + input.lead = toRecordOrNull(data?.lead) + input.campaign = toRecordOrNull(data?.campaign) + input.senderEmail = renameTypeField(data?.sender_email, 'account_type') + } + + if ( + triggerId === 'emailbison_lead_replied' || + triggerId === 'emailbison_lead_interested' || + triggerId === 'emailbison_email_bounced' + ) { + input.reply = renameTypeField(data?.reply, 'reply_type') + input.campaignEvent = renameTypeField(data?.campaign_event, 'event_type') + input.lead = toRecordOrNull(data?.lead) + input.campaign = toRecordOrNull(data?.campaign) + input.scheduledEmail = toRecordOrNull(data?.scheduled_email) + input.senderEmail = renameTypeField(data?.sender_email, 'account_type') + } + + if (triggerId === 'emailbison_untracked_reply_received') { + input.reply = renameTypeField(data?.reply, 'reply_type') + input.senderEmail = renameTypeField(data?.sender_email, 'account_type') + } + + if ( + triggerId === 'emailbison_email_account_added' || + triggerId === 'emailbison_email_account_removed' || + triggerId === 'emailbison_email_account_disconnected' || + triggerId === 'emailbison_email_account_reconnected' || + triggerId === 'emailbison_warmup_disabled_receiving_bounces' || + triggerId === 'emailbison_warmup_disabled_causing_bounces' + ) { + input.senderEmail = renameTypeField(data?.sender_email, 'account_type') + } + + if (triggerId === 'emailbison_manual_email_sent') { + input.reply = renameTypeField(data?.reply, 'reply_type') + input.lead = toRecordOrNull(data?.lead) + input.campaign = toRecordOrNull(data?.campaign) + input.scheduledEmail = toRecordOrNull(data?.scheduled_email) + input.senderEmail = renameTypeField(data?.sender_email, 'account_type') + } + + if (triggerId === 'emailbison_tag_attached' || triggerId === 'emailbison_tag_removed') { + input.tagId = toNumberOrNull(data?.tag_id) + input.tagName = toStringOrNull(data?.tag_name) + input.taggableId = toNumberOrNull(data?.taggable_id) + input.taggableType = toStringOrNull(data?.taggable_type) + } + + return { + input, + } + }, + + async createSubscription(ctx: SubscriptionContext): Promise { + const { webhook, requestId } = ctx + const providerConfig = getProviderConfig(webhook) + const apiKey = providerConfig.apiKey as string | undefined + const apiBaseUrl = providerConfig.apiBaseUrl as string | undefined + const triggerId = providerConfig.triggerId as string | undefined + + if (!apiKey?.trim()) { + throw new Error('Email Bison API Key is required.') + } + + if (!apiBaseUrl?.trim()) { + throw new Error('Email Bison Instance URL is required.') + } + + if (!triggerId) { + throw new Error('Email Bison trigger ID is required.') + } + + const { getEmailBisonEventTypeForTrigger } = await import('@/triggers/emailbison/utils') + const eventType = getEmailBisonEventTypeForTrigger(triggerId) + if (!eventType) { + throw new Error(`Unknown Email Bison trigger type: ${triggerId}`) + } + + const notificationUrl = getNotificationUrl(webhook) + + logger.info(`[${requestId}] Creating Email Bison webhook`, { + triggerId, + eventType, + webhookId: webhook.id, + }) + + const response = await fetch(emailBisonUrl('/api/webhook-url', {}, apiBaseUrl), { + method: 'POST', + headers: emailBisonHeaders({ apiKey, apiBaseUrl }), + body: JSON.stringify({ + name: `Sim - ${eventType}`, + url: notificationUrl, + events: [eventType], + }), + }) + + const responseBody = await parseJsonResponse(response) + if (!response.ok) { + const message = extractEmailBisonError(responseBody) + logger.error(`[${requestId}] Failed to create Email Bison webhook`, { + status: response.status, + message, + response: responseBody, + }) + + if (response.status === 401 || response.status === 403) { + throw new Error( + 'Invalid Email Bison API Key or Instance URL. Confirm both came from the same Email Bison instance.' + ) + } + + throw new Error( + message ? `Email Bison error: ${message}` : 'Failed to create Email Bison webhook' + ) + } + + const data = isRecord(responseBody?.data) ? responseBody.data : null + const externalId = data?.id + if (externalId === undefined || externalId === null || externalId === '') { + throw new Error('Email Bison webhook was created but the API response did not include an ID.') + } + + logger.info(`[${requestId}] Successfully created Email Bison webhook`, { + externalId, + webhookId: webhook.id, + }) + + return { providerConfigUpdates: { externalId: String(externalId) } } + }, + + async deleteSubscription(ctx: DeleteSubscriptionContext): Promise { + const { webhook, requestId } = ctx + + try { + const providerConfig = getProviderConfig(webhook) + const apiKey = providerConfig.apiKey as string | undefined + const apiBaseUrl = providerConfig.apiBaseUrl as string | undefined + const externalId = providerConfig.externalId as string | undefined + + if (!apiKey?.trim() || !apiBaseUrl?.trim() || !externalId?.trim()) { + logger.warn(`[${requestId}] Missing Email Bison webhook cleanup configuration`, { + webhookId: webhook.id, + hasApiKey: Boolean(apiKey), + hasApiBaseUrl: Boolean(apiBaseUrl), + hasExternalId: Boolean(externalId), + }) + return + } + + const response = await fetch( + emailBisonUrl(`/api/webhook-url/${encodeURIComponent(externalId)}`, {}, apiBaseUrl), + { + method: 'DELETE', + headers: emailBisonHeaders({ apiKey, apiBaseUrl }), + } + ) + + if (!response.ok && response.status !== 404) { + const responseBody = await parseJsonResponse(response) + logger.warn(`[${requestId}] Failed to delete Email Bison webhook`, { + status: response.status, + response: responseBody, + }) + return + } + + await response.body?.cancel() + logger.info(`[${requestId}] Successfully deleted Email Bison webhook`, { + externalId, + webhookId: webhook.id, + }) + } catch (error) { + logger.warn(`[${requestId}] Error deleting Email Bison webhook`, { + message: toError(error).message, + }) + } + }, +} + +async function parseJsonResponse(response: Response): Promise | null> { + try { + const body: unknown = await response.json() + return isRecord(body) ? body : null + } catch { + return null + } +} + +function extractEmailBisonError(body: Record | null): string | null { + if (!body) return null + if (typeof body.message === 'string') return body.message + if (typeof body.error === 'string') return body.error + + const data = body.data + if (isRecord(data) && typeof data.message === 'string') return data.message + + return null +} + +function unwrapEmailBisonPayload(body: Record): Record { + if (isRecord(body.event)) return body + + const data = body.data + if (isRecord(data) && isRecord(data.event)) return data + + const payload = isRecord(data) ? data.payload : body.payload + if (isRecord(payload) && isRecord(payload.event)) return payload + + return body +} + +function toStringOrNull(value: unknown): string | null { + if (value === undefined || value === null) return null + return String(value) +} + +function toNumberOrNull(value: unknown): number | null { + if (typeof value === 'number') return Number.isFinite(value) ? value : null + if (typeof value !== 'string' || value.trim() === '') return null + + const parsed = Number(value) + return Number.isFinite(parsed) ? parsed : null +} + +function toRecordOrNull(value: unknown): Record | null { + return isRecord(value) ? value : null +} + +function renameTypeField(value: unknown, targetKey: string): Record | null { + if (!isRecord(value)) return null + + const { type, ...rest } = value + return { ...rest, [targetKey]: type ?? null } +} + +function isRecord(value: unknown): value is Record { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value) +} diff --git a/apps/sim/lib/webhooks/providers/registry.ts b/apps/sim/lib/webhooks/providers/registry.ts index c20b58f3f02..ce8e9c6af73 100644 --- a/apps/sim/lib/webhooks/providers/registry.ts +++ b/apps/sim/lib/webhooks/providers/registry.ts @@ -7,6 +7,7 @@ import { calcomHandler } from '@/lib/webhooks/providers/calcom' import { calendlyHandler } from '@/lib/webhooks/providers/calendly' import { circlebackHandler } from '@/lib/webhooks/providers/circleback' import { confluenceHandler } from '@/lib/webhooks/providers/confluence' +import { emailBisonHandler } from '@/lib/webhooks/providers/emailbison' import { fathomHandler } from '@/lib/webhooks/providers/fathom' import { firefliesHandler } from '@/lib/webhooks/providers/fireflies' import { genericHandler } from '@/lib/webhooks/providers/generic' @@ -55,6 +56,7 @@ const PROVIDER_HANDLERS: Record = { calcom: calcomHandler, circleback: circlebackHandler, confluence: confluenceHandler, + emailbison: emailBisonHandler, fireflies: firefliesHandler, generic: genericHandler, gmail: gmailHandler, diff --git a/apps/sim/tools/emailbison/attach_leads_to_campaign.ts b/apps/sim/tools/emailbison/attach_leads_to_campaign.ts index b8151b66f04..79a9a68d5d8 100644 --- a/apps/sim/tools/emailbison/attach_leads_to_campaign.ts +++ b/apps/sim/tools/emailbison/attach_leads_to_campaign.ts @@ -5,6 +5,7 @@ import type { import { actionOutput, actionOutputs, + emailBisonBaseParamFields, emailBisonHeaders, emailBisonRecordData, emailBisonUrl, @@ -21,12 +22,7 @@ export const attachLeadsToCampaignTool: ToolConfig< description: 'Adds existing Email Bison leads to a campaign.', version: '1.0.0', params: { - apiKey: { - type: 'string', - required: true, - visibility: 'user-only', - description: 'Email Bison API token', - }, + ...emailBisonBaseParamFields, campaignId: { type: 'number', required: true, @@ -48,7 +44,12 @@ export const attachLeadsToCampaignTool: ToolConfig< }, }, request: { - url: (params) => emailBisonUrl(`/api/campaigns/${params.campaignId}/leads/attach-leads`), + url: (params) => + emailBisonUrl( + `/api/campaigns/${params.campaignId}/leads/attach-leads`, + {}, + params.apiBaseUrl + ), method: 'POST', headers: emailBisonHeaders, body: (params) => diff --git a/apps/sim/tools/emailbison/attach_tags_to_leads.ts b/apps/sim/tools/emailbison/attach_tags_to_leads.ts index 1943016a1f5..ff78161b6d5 100644 --- a/apps/sim/tools/emailbison/attach_tags_to_leads.ts +++ b/apps/sim/tools/emailbison/attach_tags_to_leads.ts @@ -5,6 +5,7 @@ import type { import { actionOutput, actionOutputs, + emailBisonBaseParamFields, emailBisonHeaders, emailBisonRecordData, emailBisonUrl, @@ -21,12 +22,7 @@ export const attachTagsToLeadsTool: ToolConfig< description: 'Attaches Email Bison tags to one or more leads.', version: '1.0.0', params: { - apiKey: { - type: 'string', - required: true, - visibility: 'user-only', - description: 'Email Bison API token', - }, + ...emailBisonBaseParamFields, tagIds: { type: 'array', required: true, @@ -49,7 +45,7 @@ export const attachTagsToLeadsTool: ToolConfig< }, }, request: { - url: () => emailBisonUrl('/api/tags/attach-to-leads'), + url: (params) => emailBisonUrl('/api/tags/attach-to-leads', {}, params.apiBaseUrl), method: 'POST', headers: emailBisonHeaders, body: (params) => diff --git a/apps/sim/tools/emailbison/create_campaign.ts b/apps/sim/tools/emailbison/create_campaign.ts index 897de0e90d7..b264cbdf7d0 100644 --- a/apps/sim/tools/emailbison/create_campaign.ts +++ b/apps/sim/tools/emailbison/create_campaign.ts @@ -4,6 +4,7 @@ import type { } from '@/tools/emailbison/types' import { campaignOutputs, + emailBisonBaseParamFields, emailBisonHeaders, emailBisonRecordData, emailBisonUrl, @@ -21,12 +22,7 @@ export const createCampaignTool: ToolConfig< description: 'Creates a new Email Bison campaign.', version: '1.0.0', params: { - apiKey: { - type: 'string', - required: true, - visibility: 'user-only', - description: 'Email Bison API token', - }, + ...emailBisonBaseParamFields, name: { type: 'string', required: true, @@ -41,7 +37,7 @@ export const createCampaignTool: ToolConfig< }, }, request: { - url: () => emailBisonUrl('/api/campaigns'), + url: (params) => emailBisonUrl('/api/campaigns', {}, params.apiBaseUrl), method: 'POST', headers: emailBisonHeaders, body: (params) => diff --git a/apps/sim/tools/emailbison/create_lead.ts b/apps/sim/tools/emailbison/create_lead.ts index 1cbf256f579..58d4051a7f3 100644 --- a/apps/sim/tools/emailbison/create_lead.ts +++ b/apps/sim/tools/emailbison/create_lead.ts @@ -1,5 +1,6 @@ import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types' import { + emailBisonBaseParamFields, emailBisonHeaders, emailBisonRecordData, emailBisonUrl, @@ -15,12 +16,7 @@ export const createLeadTool: ToolConfig emailBisonUrl('/api/leads'), + url: (params) => emailBisonUrl('/api/leads', {}, params.apiBaseUrl), method: 'POST', headers: emailBisonHeaders, body: (params) => diff --git a/apps/sim/tools/emailbison/create_tag.ts b/apps/sim/tools/emailbison/create_tag.ts index 6488483895c..7086dd699ff 100644 --- a/apps/sim/tools/emailbison/create_tag.ts +++ b/apps/sim/tools/emailbison/create_tag.ts @@ -1,5 +1,6 @@ import type { EmailBisonCreateTagParams, EmailBisonTagResponse } from '@/tools/emailbison/types' import { + emailBisonBaseParamFields, emailBisonHeaders, emailBisonRecordData, emailBisonUrl, @@ -15,12 +16,7 @@ export const createTagTool: ToolConfig emailBisonUrl('/api/tags'), + url: (params) => emailBisonUrl('/api/tags', {}, params.apiBaseUrl), method: 'POST', headers: emailBisonHeaders, body: (params) => jsonBody({ name: params.name }), diff --git a/apps/sim/tools/emailbison/get_lead.ts b/apps/sim/tools/emailbison/get_lead.ts index 90a3fb73fa7..02353615592 100644 --- a/apps/sim/tools/emailbison/get_lead.ts +++ b/apps/sim/tools/emailbison/get_lead.ts @@ -1,5 +1,6 @@ import type { EmailBisonGetLeadParams, EmailBisonLeadResponse } from '@/tools/emailbison/types' import { + emailBisonBaseParamFields, emailBisonHeaders, emailBisonRecordData, emailBisonUrl, @@ -14,12 +15,7 @@ export const getLeadTool: ToolConfig emailBisonUrl(`/api/leads/${encodeURIComponent(params.leadId.trim())}`), + url: (params) => + emailBisonUrl( + `/api/leads/${encodeURIComponent(params.leadId.trim())}`, + {}, + params.apiBaseUrl + ), method: 'GET', headers: emailBisonHeaders, }, diff --git a/apps/sim/tools/emailbison/list_campaigns.ts b/apps/sim/tools/emailbison/list_campaigns.ts index 1dc3093c708..3987ac6116e 100644 --- a/apps/sim/tools/emailbison/list_campaigns.ts +++ b/apps/sim/tools/emailbison/list_campaigns.ts @@ -4,6 +4,7 @@ import type { } from '@/tools/emailbison/types' import { emailBisonArrayData, + emailBisonBaseParamFields, emailBisonHeaders, emailBisonUrl, listCampaignsOutputs, @@ -18,15 +19,10 @@ export const listCampaignsTool: ToolConfig emailBisonUrl('/api/campaigns'), + url: (params) => emailBisonUrl('/api/campaigns', {}, params.apiBaseUrl), method: 'GET', headers: emailBisonHeaders, }, diff --git a/apps/sim/tools/emailbison/list_leads.ts b/apps/sim/tools/emailbison/list_leads.ts index b12e7fb3d7f..b79b6bb0b8c 100644 --- a/apps/sim/tools/emailbison/list_leads.ts +++ b/apps/sim/tools/emailbison/list_leads.ts @@ -4,6 +4,7 @@ import type { } from '@/tools/emailbison/types' import { emailBisonArrayData, + emailBisonBaseParamFields, emailBisonHeaders, emailBisonUrl, listLeadsOutputs, @@ -17,12 +18,7 @@ export const listLeadsTool: ToolConfig - emailBisonUrl('/api/leads', { - search: params.search, - 'filters.lead_campaign_status': params.campaignStatus, - 'filters.tag_ids': params.tagIds, - 'filters.excluded_tag_ids': params.excludedTagIds, - 'filters.without_tags': params.withoutTags, - }), + emailBisonUrl( + '/api/leads', + { + search: params.search, + 'filters.lead_campaign_status': params.campaignStatus, + 'filters.tag_ids': params.tagIds, + 'filters.excluded_tag_ids': params.excludedTagIds, + 'filters.without_tags': params.withoutTags, + }, + params.apiBaseUrl + ), method: 'GET', headers: emailBisonHeaders, }, diff --git a/apps/sim/tools/emailbison/list_replies.ts b/apps/sim/tools/emailbison/list_replies.ts index 6f075c5e188..97f3771832f 100644 --- a/apps/sim/tools/emailbison/list_replies.ts +++ b/apps/sim/tools/emailbison/list_replies.ts @@ -4,6 +4,7 @@ import type { } from '@/tools/emailbison/types' import { emailBisonArrayData, + emailBisonBaseParamFields, emailBisonHeaders, emailBisonUrl, listRepliesOutputs, @@ -21,12 +22,7 @@ export const listRepliesTool: ToolConfig< 'Retrieves Email Bison replies with optional status, folder, campaign, sender, lead, and tag filters.', version: '1.0.0', params: { - apiKey: { - type: 'string', - required: true, - visibility: 'user-only', - description: 'Email Bison API token', - }, + ...emailBisonBaseParamFields, search: { type: 'string', required: false, @@ -79,16 +75,20 @@ export const listRepliesTool: ToolConfig< }, request: { url: (params) => - emailBisonUrl('/api/replies', { - search: params.search, - status: params.status, - folder: params.folder, - read: params.read, - campaign_id: params.campaignId, - sender_email_id: params.senderEmailId, - lead_id: params.leadId, - tag_ids: params.tagIds, - }), + emailBisonUrl( + '/api/replies', + { + search: params.search, + status: params.status, + folder: params.folder, + read: params.read, + campaign_id: params.campaignId, + sender_email_id: params.senderEmailId, + lead_id: params.leadId, + tag_ids: params.tagIds, + }, + params.apiBaseUrl + ), method: 'GET', headers: emailBisonHeaders, }, diff --git a/apps/sim/tools/emailbison/list_tags.ts b/apps/sim/tools/emailbison/list_tags.ts index 747ab53e277..f0811da7737 100644 --- a/apps/sim/tools/emailbison/list_tags.ts +++ b/apps/sim/tools/emailbison/list_tags.ts @@ -1,6 +1,7 @@ import type { EmailBisonBaseParams, EmailBisonListTagsResponse } from '@/tools/emailbison/types' import { emailBisonArrayData, + emailBisonBaseParamFields, emailBisonHeaders, emailBisonUrl, listTagsOutputs, @@ -14,15 +15,10 @@ export const listTagsTool: ToolConfig emailBisonUrl('/api/tags'), + url: (params) => emailBisonUrl('/api/tags', {}, params.apiBaseUrl), method: 'GET', headers: emailBisonHeaders, }, diff --git a/apps/sim/tools/emailbison/types.ts b/apps/sim/tools/emailbison/types.ts index 2b357f446ae..37977309c6d 100644 --- a/apps/sim/tools/emailbison/types.ts +++ b/apps/sim/tools/emailbison/types.ts @@ -2,6 +2,7 @@ import type { ToolResponse } from '@/tools/types' export interface EmailBisonBaseParams { apiKey: string + apiBaseUrl: string } export interface EmailBisonCustomVariable { diff --git a/apps/sim/tools/emailbison/update_campaign.ts b/apps/sim/tools/emailbison/update_campaign.ts index 48ea126b10c..2ff767b9001 100644 --- a/apps/sim/tools/emailbison/update_campaign.ts +++ b/apps/sim/tools/emailbison/update_campaign.ts @@ -4,6 +4,7 @@ import type { } from '@/tools/emailbison/types' import { campaignOutputs, + emailBisonBaseParamFields, emailBisonHeaders, emailBisonRecordData, emailBisonUrl, @@ -21,12 +22,7 @@ export const updateCampaignTool: ToolConfig< description: 'Updates Email Bison campaign settings.', version: '1.0.0', params: { - apiKey: { - type: 'string', - required: true, - visibility: 'user-only', - description: 'Email Bison API token', - }, + ...emailBisonBaseParamFields, campaignId: { type: 'number', required: true, @@ -89,7 +85,8 @@ export const updateCampaignTool: ToolConfig< }, }, request: { - url: (params) => emailBisonUrl(`/api/campaigns/${params.campaignId}/update`), + url: (params) => + emailBisonUrl(`/api/campaigns/${params.campaignId}/update`, {}, params.apiBaseUrl), method: 'PATCH', headers: emailBisonHeaders, body: (params) => diff --git a/apps/sim/tools/emailbison/update_campaign_status.ts b/apps/sim/tools/emailbison/update_campaign_status.ts index 61fbc5dab8e..951eb556fbf 100644 --- a/apps/sim/tools/emailbison/update_campaign_status.ts +++ b/apps/sim/tools/emailbison/update_campaign_status.ts @@ -4,6 +4,7 @@ import type { } from '@/tools/emailbison/types' import { campaignOutputs, + emailBisonBaseParamFields, emailBisonHeaders, emailBisonRecordData, emailBisonUrl, @@ -20,12 +21,7 @@ export const updateCampaignStatusTool: ToolConfig< description: 'Pauses, resumes, or archives an Email Bison campaign.', version: '1.0.0', params: { - apiKey: { - type: 'string', - required: true, - visibility: 'user-only', - description: 'Email Bison API token', - }, + ...emailBisonBaseParamFields, campaignId: { type: 'number', required: true, @@ -40,7 +36,8 @@ export const updateCampaignStatusTool: ToolConfig< }, }, request: { - url: (params) => emailBisonUrl(`/api/campaigns/${params.campaignId}/${params.action}`), + url: (params) => + emailBisonUrl(`/api/campaigns/${params.campaignId}/${params.action}`, {}, params.apiBaseUrl), method: 'PATCH', headers: emailBisonHeaders, }, diff --git a/apps/sim/tools/emailbison/update_lead.ts b/apps/sim/tools/emailbison/update_lead.ts index 7db7edb6fe1..fb423d8b9b5 100644 --- a/apps/sim/tools/emailbison/update_lead.ts +++ b/apps/sim/tools/emailbison/update_lead.ts @@ -1,5 +1,6 @@ import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types' import { + emailBisonBaseParamFields, emailBisonHeaders, emailBisonRecordData, emailBisonUrl, @@ -16,12 +17,7 @@ export const updateLeadTool: ToolConfig emailBisonUrl(`/api/leads/${encodeURIComponent(params.leadId?.trim() ?? '')}`), + url: (params) => + emailBisonUrl( + `/api/leads/${encodeURIComponent(params.leadId?.trim() ?? '')}`, + {}, + params.apiBaseUrl + ), method: 'PUT', headers: emailBisonHeaders, body: (params) => diff --git a/apps/sim/tools/emailbison/utils.ts b/apps/sim/tools/emailbison/utils.ts index f14ba3bbcd6..844d6867dca 100644 --- a/apps/sim/tools/emailbison/utils.ts +++ b/apps/sim/tools/emailbison/utils.ts @@ -11,8 +11,6 @@ import type { } from '@/tools/emailbison/types' import type { OutputProperty, ToolConfig } from '@/tools/types' -const EMAILBISON_API_BASE_URL = 'https://dedi.emailbison.com' - type QueryValue = string | number | boolean | Array | undefined | null interface EmailBisonEnvelope { @@ -21,13 +19,32 @@ interface EmailBisonEnvelope { export function emailBisonHeaders(params: EmailBisonBaseParams): Record { return { - Authorization: `Bearer ${params.apiKey}`, + Authorization: `Bearer ${params.apiKey.trim()}`, 'Content-Type': 'application/json', } } -export function emailBisonUrl(path: string, query: Record = {}): string { - const url = new URL(path, EMAILBISON_API_BASE_URL) +export const emailBisonBaseParamFields = { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison API token', + }, + apiBaseUrl: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Email Bison instance URL that issued the token', + }, +} satisfies ToolConfig['params'] + +export function emailBisonUrl( + path: string, + query: Record, + baseUrl: string +): string { + const url = new URL(path, normalizeEmailBisonBaseUrl(baseUrl)) Object.entries(query).forEach(([key, value]) => { if (value === undefined || value === null || value === '') return @@ -45,6 +62,24 @@ export function emailBisonUrl(path: string, query: Record = return url.toString() } +function normalizeEmailBisonBaseUrl(baseUrl: string): string { + const trimmedBaseUrl = baseUrl.trim() + if (!trimmedBaseUrl) { + throw new Error('Email Bison Instance URL is required') + } + + const rawBaseUrl = /^https?:\/\//i.test(trimmedBaseUrl) + ? trimmedBaseUrl + : `https://${trimmedBaseUrl}` + const parsed = new URL(rawBaseUrl) + + if (parsed.protocol !== 'https:') { + throw new Error('Email Bison Instance URL must use HTTPS') + } + + return parsed.origin +} + export function jsonBody(fields: Record): Record { return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined)) } diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 4b5bca95866..0c8a964aa5c 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -5275,13 +5275,13 @@ export const tools: Record = { hubspot_update_deal: hubspotUpdateDealTool, hubspot_update_line_item: hubspotUpdateLineItemTool, hubspot_update_ticket: hubspotUpdateTicketTool, + sharepoint_add_list_items: sharepointAddListItemTool, + sharepoint_create_list: sharepointCreateListTool, sharepoint_create_page: sharepointCreatePageTool, - sharepoint_read_page: sharepointReadPageTool, - sharepoint_list_sites: sharepointListSitesTool, sharepoint_get_list: sharepointGetListTool, - sharepoint_create_list: sharepointCreateListTool, + sharepoint_list_sites: sharepointListSitesTool, + sharepoint_read_page: sharepointReadPageTool, sharepoint_update_list: sharepointUpdateListItemTool, - sharepoint_add_list_items: sharepointAddListItemTool, sharepoint_upload_file: sharepointUploadFileTool, stripe_create_payment_intent: stripeCreatePaymentIntentTool, stripe_retrieve_payment_intent: stripeRetrievePaymentIntentTool, diff --git a/apps/sim/tools/sharepoint/add_list_items.ts b/apps/sim/tools/sharepoint/add_list_items.ts index 8f2bb197597..ab3afdf3bea 100644 --- a/apps/sim/tools/sharepoint/add_list_items.ts +++ b/apps/sim/tools/sharepoint/add_list_items.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import type { SharepointAddListItemResponse, SharepointToolParams } from '@/tools/sharepoint/types' +import { optionalTrim } from '@/tools/sharepoint/utils' import type { ToolConfig } from '@/tools/types' const logger = createLogger('SharePointAddListItem') @@ -8,7 +9,7 @@ export const addListItemTool: ToolConfig { - const siteId = params.siteId || params.siteSelector || 'root' - if (!params.listId) { + const siteId = optionalTrim(params.siteId) || optionalTrim(params.siteSelector) || 'root' + const listId = optionalTrim(params.listId) + if (!listId) { throw new Error('listId must be provided') } - const listSegment = params.listId + const listSegment = encodeURIComponent(listId) return `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listSegment}/items` }, method: 'POST', @@ -74,7 +76,10 @@ export const addListItemTool: ToolConfig) && Object.keys(params.listItemFields as Record).length === 1 - ? ((params.listItemFields as any).fields as Record) + ? ((params.listItemFields as { fields: Record }).fields as Record< + string, + unknown + >) : (params.listItemFields as Record) if (!providedFields || Object.keys(providedFields).length === 0) { diff --git a/apps/sim/tools/sharepoint/create_list.ts b/apps/sim/tools/sharepoint/create_list.ts index 18d949ac7cd..007631b207e 100644 --- a/apps/sim/tools/sharepoint/create_list.ts +++ b/apps/sim/tools/sharepoint/create_list.ts @@ -5,6 +5,7 @@ import type { SharepointList, SharepointToolParams, } from '@/tools/sharepoint/types' +import { optionalTrim } from '@/tools/sharepoint/utils' import type { ToolConfig } from '@/tools/types' const logger = createLogger('SharePointCreateList') @@ -13,7 +14,7 @@ export const createListTool: ToolConfig { - const siteId = params.siteSelector || params.siteId || 'root' + const siteId = optionalTrim(params.siteSelector) || optionalTrim(params.siteId) || 'root' return `https://graph.microsoft.com/v1.0/sites/${siteId}/lists` }, method: 'POST', @@ -79,36 +80,51 @@ export const createListTool: ToolConfig { - if (!params.listDisplayName) { + const listDisplayName = optionalTrim(params.listDisplayName) + if (!listDisplayName) { throw new Error('listDisplayName is required') } - // Derive columns from pageContent JSON (object or string) or top-level array let columns: unknown[] | undefined if (params.pageContent) { if (typeof params.pageContent === 'string') { try { const parsed = JSON.parse(params.pageContent) - if (Array.isArray(parsed)) columns = parsed - else if (parsed && Array.isArray((parsed as any).columns)) - columns = (parsed as any).columns + if (Array.isArray(parsed)) { + columns = parsed + } else if ( + parsed && + typeof parsed === 'object' && + Array.isArray((parsed as { columns?: unknown[] }).columns) + ) { + columns = (parsed as { columns: unknown[] }).columns + } } catch (error) { logger.warn('Invalid JSON in pageContent for create list; ignoring', { error: toError(error).message, }) } } else if (typeof params.pageContent === 'object') { - const pc: any = params.pageContent - if (Array.isArray(pc)) columns = pc - else if (pc && Array.isArray(pc.columns)) columns = pc.columns + const pageContent = params.pageContent as { columns?: unknown[] } | unknown[] + if (Array.isArray(pageContent)) { + columns = pageContent + } else if (pageContent && Array.isArray(pageContent.columns)) { + columns = pageContent.columns + } } } - const payload: any = { - displayName: params.listDisplayName, - description: params.listDescription, - list: { template: params.listTemplate || 'genericList' }, + const payload: { + displayName: string + description?: string + list: { template: string } + columns?: unknown[] + } = { + displayName: listDisplayName, + list: { template: optionalTrim(params.listTemplate) || 'genericList' }, } + const listDescription = optionalTrim(params.listDescription) + if (listDescription) payload.description = listDescription if (columns && columns.length > 0) payload.columns = columns logger.info('Creating SharePoint list', { diff --git a/apps/sim/tools/sharepoint/create_page.ts b/apps/sim/tools/sharepoint/create_page.ts index c5bea57bb10..32a1e169269 100644 --- a/apps/sim/tools/sharepoint/create_page.ts +++ b/apps/sim/tools/sharepoint/create_page.ts @@ -4,6 +4,7 @@ import type { SharepointPage, SharepointToolParams, } from '@/tools/sharepoint/types' +import { optionalTrim } from '@/tools/sharepoint/utils' import type { ToolConfig } from '@/tools/types' const logger = createLogger('SharePointCreatePage') @@ -12,7 +13,7 @@ export const createPageTool: ToolConfig { - // Use specific site if provided, otherwise use root site - const siteId = params.siteSelector || params.siteId || 'root' + const siteId = optionalTrim(params.siteSelector) || optionalTrim(params.siteId) || 'root' return `https://graph.microsoft.com/v1.0/sites/${siteId}/pages` }, method: 'POST', @@ -71,16 +71,16 @@ export const createPageTool: ToolConfig { - if (!params.pageName) { + const pageName = optionalTrim(params.pageName) + if (!pageName) { throw new Error('Page name is required') } - const pageTitle = params.pageTitle || params.pageName + const pageTitle = optionalTrim(params.pageTitle) || pageName - // Basic page structure required by Microsoft Graph API const pageData: SharepointPage = { '@odata.type': '#microsoft.graph.sitePage', - name: params.pageName, + name: pageName, title: pageTitle, publishingState: { level: 'draft', @@ -88,8 +88,8 @@ export const createPageTool: ToolConfig${params.pageContent.replace(/"/g, '"').replace(/'/g, ''')}

`, + innerHtml: `

${pageContent.replace(/"/g, '"').replace(/'/g, ''')}

`, }, ], }, diff --git a/apps/sim/tools/sharepoint/get_list.ts b/apps/sim/tools/sharepoint/get_list.ts index f5528ee95b6..bcf3036a977 100644 --- a/apps/sim/tools/sharepoint/get_list.ts +++ b/apps/sim/tools/sharepoint/get_list.ts @@ -4,6 +4,7 @@ import type { SharepointList, SharepointToolParams, } from '@/tools/sharepoint/types' +import { assertGraphNextPageUrl, getGraphNextPageUrl, optionalTrim } from '@/tools/sharepoint/utils' import type { ToolConfig } from '@/tools/types' const logger = createLogger('SharePointGetList') @@ -12,7 +13,7 @@ export const getListTool: ToolConfig { - const siteId = params.siteId || params.siteSelector || 'root' + if (params.nextPageUrl) { + return assertGraphNextPageUrl(params.nextPageUrl) + } + + const siteId = optionalTrim(params.siteId) || optionalTrim(params.siteSelector) || 'root' + const listId = optionalTrim(params.listId) - // If neither listId nor listTitle provided, list all lists in the site - if (!params.listId) { + if (!listId) { const baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists` const url = new URL(baseUrl) const finalUrl = url.toString() @@ -63,11 +86,9 @@ export const getListTool: ToolConfig { + transformResponse: async (response: Response, params) => { const data = await response.json() // If the response is a collection of items (from the items endpoint) @@ -122,25 +142,18 @@ export const getListTool: ToolConfig, })) - const nextLink: string | undefined = (data as any)['@odata.nextLink'] - const nextPageToken = nextLink - ? (() => { - try { - const u = new URL(nextLink) - return u.searchParams.get('$skiptoken') || u.searchParams.get('$skip') || undefined - } catch { - return undefined - } - })() - : undefined + const nextPageUrl = getGraphNextPageUrl(data as Record) return { success: true, - output: { list: { items } as SharepointList, nextPageToken }, + output: { + list: { id: optionalTrim(params?.listId) || '', items } as SharepointList, + items, + nextPageUrl, + }, } } - // If this is a collection of lists (site-level) if (Array.isArray((data as any).value)) { const lists: SharepointList[] = (data as any).value.map((l: any) => ({ id: l.id, @@ -152,25 +165,14 @@ export const getListTool: ToolConfig { - try { - const u = new URL(nextLink) - return u.searchParams.get('$skiptoken') || u.searchParams.get('$skip') || undefined - } catch { - return undefined - } - })() - : undefined + const nextPageUrl = getGraphNextPageUrl(data as Record) return { success: true, - output: { lists, nextPageToken }, + output: { lists, nextPageUrl }, } } - // Single list response (with optional expands) const list: SharepointList = { id: data.id, displayName: data.displayName ?? data.name, @@ -242,5 +244,21 @@ export const getListTool: ToolConfig = { id: 'sharepoint_list_sites', name: 'List SharePoint Sites', description: 'List details of all SharePoint sites', - version: '1.0', + version: '1.0.0', oauth: { required: true, @@ -29,6 +30,12 @@ export const listSitesTool: ToolConfig { + if (params.nextPageUrl) { + return assertGraphNextPageUrl(params.nextPageUrl) + } + let baseUrl: string + const groupId = optionalTrim(params.groupId) + const siteId = optionalTrim(params.siteId) || optionalTrim(params.siteSelector) - if (params.groupId) { - // Access group team site - baseUrl = `https://graph.microsoft.com/v1.0/groups/${params.groupId}/sites/root` - } else if (params.siteId || params.siteSelector) { - // Access specific site by ID - const siteId = params.siteId || params.siteSelector + if (groupId) { + baseUrl = `https://graph.microsoft.com/v1.0/groups/${groupId}/sites/root` + } else if (siteId) { baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}` } else { - // get all sites baseUrl = 'https://graph.microsoft.com/v1.0/sites?search=*' } const url = new URL(baseUrl) - // Use Microsoft Graph $select parameter to get site details url.searchParams.append( '$select', 'id,name,displayName,webUrl,description,createdDateTime,lastModifiedDateTime,isPersonalSite,root,siteCollection' @@ -71,12 +85,10 @@ export const listSitesTool: ToolConfig { + transformResponse: async (response: Response) => { const data = await response.json() - // Check if this is a search result (multiple sites) or single site if (data.value && Array.isArray(data.value)) { - // Multiple sites from search return { success: true, output: { @@ -89,10 +101,11 @@ export const listSitesTool: ToolConfig), }, } } - // Single site response + return { success: true, output: { @@ -155,5 +168,10 @@ export const listSitesTool: ToolConfig { - // Use specific site if provided, otherwise use root site - const siteId = params.siteId || params.siteSelector || 'root' + if (params.nextPageUrl) { + return assertGraphNextPageUrl(params.nextPageUrl) + } + + const siteId = optionalTrim(params.siteId) || optionalTrim(params.siteSelector) || 'root' + const pageId = optionalTrim(params.pageId) let baseUrl: string - if (params.pageId) { - // Read specific page by ID - baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/pages/${params.pageId}` + if (pageId) { + baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/pages/${pageId}/microsoft.graph.sitePage` } else { - // List all pages (with optional filtering by name) - baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/pages` + baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/pages/microsoft.graph.sitePage` } const url = new URL(baseUrl) - // Use Microsoft Graph $select parameter to get page details - // Only include valid properties for SharePoint pages url.searchParams.append( '$select', - 'id,name,title,webUrl,pageLayout,createdDateTime,lastModifiedDateTime' + 'id,name,title,webUrl,pageLayout,description,createdDateTime,lastModifiedDateTime' ) - // If searching by name, add filter - if (params.pageName && !params.pageId) { - // Try to handle both with and without .aspx extension - const pageName = params.pageName + if (params.pageName && !pageId) { + const pageName = params.pageName.trim() const pageNameWithAspx = pageName.endsWith('.aspx') ? pageName : `${pageName}.aspx` - - // Search for exact match first, then with .aspx if needed - url.searchParams.append('$filter', `name eq '${pageName}' or name eq '${pageNameWithAspx}'`) - url.searchParams.append('$top', '10') // Get more results to find matches - } else if (!params.pageId && !params.pageName) { - // When listing all pages, apply maxPages limit - const maxPages = Math.min(params.maxPages || 10, 50) // Default 10, max 50 + const escapedPageName = escapeODataString(pageName) + const escapedPageNameWithAspx = escapeODataString(pageNameWithAspx) + + url.searchParams.append( + '$filter', + `name eq '${escapedPageName}' or name eq '${escapedPageNameWithAspx}'` + ) + url.searchParams.append('$top', '10') + } else if (!pageId && !params.pageName) { + const requestedMaxPages = + typeof params.maxPages === 'number' ? params.maxPages : Number(params.maxPages || 10) + const maxPages = Math.min(Number.isFinite(requestedMaxPages) ? requestedMaxPages : 10, 50) url.searchParams.append('$top', maxPages.toString()) } - // Only expand content when getting a specific page by ID - if (params.pageId) { + if (pageId) { url.searchParams.append('$expand', 'canvasLayout') } @@ -112,7 +127,7 @@ export const readPageTool: ToolConfig) logger.info('Fetching content for all pages', { totalPages: data.value.length, siteId, }) - // Fetch content for each page for (const pageInfo of data.value) { const contentUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/pages/${pageInfo.id}/microsoft.graph.sitePage?$expand=canvasLayout` @@ -280,6 +295,7 @@ export const readPageTool: ToolConfig + nextPageUrl?: string } } @@ -155,12 +158,12 @@ export interface SharepointToolParams { siteSelector?: string pageId?: string pageName?: string - pageContent?: string + pageContent?: string | unknown[] | { columns?: unknown[] } pageTitle?: string publishingState?: string query?: string pageSize?: number - pageToken?: string + nextPageUrl?: string hostname?: string serverRelativePath?: string groupId?: string @@ -188,6 +191,7 @@ export interface GraphApiResponse { id?: string name?: string title?: string + description?: string | null webUrl?: string pageLayout?: string createdDateTime?: string @@ -203,6 +207,7 @@ export interface GraphApiPageItem { id: string name: string title?: string + description?: string | null webUrl?: string pageLayout?: string createdDateTime?: string @@ -227,36 +232,6 @@ export interface CanvasLayout { }> } -export interface SharepointReadSiteResponse extends ToolResponse { - output: { - site?: { - id: string - name: string - displayName: string - webUrl: string - description?: string - createdDateTime?: string - lastModifiedDateTime?: string - isPersonalSite?: boolean - root?: { - serverRelativeUrl: string - } - siteCollection?: { - hostname: string - } - } - sites?: Array<{ - id: string - name: string - displayName: string - webUrl: string - description?: string - createdDateTime?: string - lastModifiedDateTime?: string - }> - } -} - export type SharepointResponse = | SharepointListSitesResponse | SharepointCreatePageResponse @@ -272,7 +247,8 @@ export interface SharepointGetListResponse extends ToolResponse { output: { list?: SharepointList lists?: SharepointList[] - nextPageToken?: string + items?: SharepointListItem[] + nextPageUrl?: string } } diff --git a/apps/sim/tools/sharepoint/update_list.ts b/apps/sim/tools/sharepoint/update_list.ts index d2a62227963..a0511e45f2b 100644 --- a/apps/sim/tools/sharepoint/update_list.ts +++ b/apps/sim/tools/sharepoint/update_list.ts @@ -3,6 +3,7 @@ import type { SharepointToolParams, SharepointUpdateListItemResponse, } from '@/tools/sharepoint/types' +import { optionalTrim } from '@/tools/sharepoint/utils' import type { ToolConfig } from '@/tools/types' const logger = createLogger('SharePointUpdateListItem') @@ -14,7 +15,7 @@ export const updateListItemTool: ToolConfig< id: 'sharepoint_update_list', name: 'Update SharePoint List Item', description: 'Update the properties (fields) on a SharePoint list item', - version: '1.0', + version: '1.0.0', oauth: { required: true, @@ -42,7 +43,7 @@ export const updateListItemTool: ToolConfig< }, listId: { type: 'string', - required: false, + required: true, visibility: 'user-or-llm', description: 'The ID of the list containing the item. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012', @@ -54,7 +55,7 @@ export const updateListItemTool: ToolConfig< description: 'The ID of the list item to update. Example: 1, 42, or 123', }, listItemFields: { - type: 'object', + type: 'json', required: true, visibility: 'user-only', description: 'Field values to update on the list item', @@ -63,13 +64,15 @@ export const updateListItemTool: ToolConfig< request: { url: (params) => { - const siteId = params.siteId || params.siteSelector || 'root' - if (!params.itemId) throw new Error('itemId is required') - if (!params.listId) { + const siteId = optionalTrim(params.siteId) || optionalTrim(params.siteSelector) || 'root' + const itemId = optionalTrim(params.itemId) + const listId = optionalTrim(params.listId) + if (!itemId) throw new Error('itemId is required') + if (!listId) { throw new Error('listId must be provided') } - const listSegment = params.listId - return `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listSegment}/items/${params.itemId}/fields` + const listSegment = encodeURIComponent(listId) + return `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listSegment}/items/${encodeURIComponent(itemId)}/fields` }, method: 'PATCH', headers: (params) => ({ diff --git a/apps/sim/tools/sharepoint/upload_file.ts b/apps/sim/tools/sharepoint/upload_file.ts index 8728e4ea673..c4c1b72cf1f 100644 --- a/apps/sim/tools/sharepoint/upload_file.ts +++ b/apps/sim/tools/sharepoint/upload_file.ts @@ -1,11 +1,12 @@ import type { SharepointToolParams, SharepointUploadFileResponse } from '@/tools/sharepoint/types' +import { optionalTrim } from '@/tools/sharepoint/utils' import type { ToolConfig } from '@/tools/types' export const uploadFileTool: ToolConfig = { id: 'sharepoint_upload_file', name: 'Upload File to SharePoint', description: 'Upload files to a SharePoint document library', - version: '1.0', + version: '1.0.0', oauth: { required: true, @@ -47,7 +48,7 @@ export const uploadFileTool: ToolConfig { return { accessToken: params.accessToken, - siteId: params.siteId || 'root', - driveId: params.driveId || null, - folderPath: params.folderPath || null, - fileName: params.fileName || null, + siteId: optionalTrim(params.siteId) || 'root', + driveId: optionalTrim(params.driveId) || null, + folderPath: optionalTrim(params.folderPath) || null, + fileName: optionalTrim(params.fileName) || null, files: params.files || null, } }, diff --git a/apps/sim/tools/sharepoint/utils.ts b/apps/sim/tools/sharepoint/utils.ts index d1188ff7b75..266821cbb67 100644 --- a/apps/sim/tools/sharepoint/utils.ts +++ b/apps/sim/tools/sharepoint/utils.ts @@ -3,6 +3,30 @@ import type { CanvasLayout } from '@/tools/sharepoint/types' const logger = createLogger('SharepointUtils') +export function optionalTrim(value: unknown): string | undefined { + if (value === undefined || value === null) return undefined + const trimmed = String(value).trim() + return trimmed || undefined +} + +export function escapeODataString(value: string): string { + return value.replace(/'/g, "''") +} + +export function getGraphNextPageUrl(data: Record): string | undefined { + const nextLink = data['@odata.nextLink'] + return typeof nextLink === 'string' ? nextLink : undefined +} + +export function assertGraphNextPageUrl(nextPageUrl: string): string { + const trimmed = nextPageUrl.trim() + const url = new URL(trimmed) + if (url.origin !== 'https://graph.microsoft.com') { + throw new Error('nextPageUrl must be a Microsoft Graph @odata.nextLink URL') + } + return url.toString() +} + function stripHtmlTags(html: string): string { let text = html let previous: string diff --git a/apps/sim/triggers/emailbison/email_account_added.ts b/apps/sim/triggers/emailbison/email_account_added.ts new file mode 100644 index 00000000000..24669f31bcc --- /dev/null +++ b/apps/sim/triggers/emailbison/email_account_added.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonEmailAccountAddedOutputs, + buildEmailBisonExtraFields, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonEmailAccountAddedTrigger: TriggerConfig = { + id: 'emailbison_email_account_added', + name: 'Email Bison Email Account Added', + provider: 'emailbison', + description: 'Trigger when a sender email account is added to Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_email_account_added', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Email Account Added'), + extraFields: buildEmailBisonExtraFields('emailbison_email_account_added'), + }), + outputs: buildEmailBisonEmailAccountAddedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/email_account_disconnected.ts b/apps/sim/triggers/emailbison/email_account_disconnected.ts new file mode 100644 index 00000000000..ea70ad995d7 --- /dev/null +++ b/apps/sim/triggers/emailbison/email_account_disconnected.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonEmailAccountDisconnectedOutputs, + buildEmailBisonExtraFields, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonEmailAccountDisconnectedTrigger: TriggerConfig = { + id: 'emailbison_email_account_disconnected', + name: 'Email Bison Email Account Disconnected', + provider: 'emailbison', + description: 'Trigger when a sender email account disconnects in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_email_account_disconnected', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Email Account Disconnected'), + extraFields: buildEmailBisonExtraFields('emailbison_email_account_disconnected'), + }), + outputs: buildEmailBisonEmailAccountDisconnectedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/email_account_reconnected.ts b/apps/sim/triggers/emailbison/email_account_reconnected.ts new file mode 100644 index 00000000000..3b05d36205c --- /dev/null +++ b/apps/sim/triggers/emailbison/email_account_reconnected.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonEmailAccountReconnectedOutputs, + buildEmailBisonExtraFields, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonEmailAccountReconnectedTrigger: TriggerConfig = { + id: 'emailbison_email_account_reconnected', + name: 'Email Bison Email Account Reconnected', + provider: 'emailbison', + description: 'Trigger when a sender email account reconnects in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_email_account_reconnected', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Email Account Reconnected'), + extraFields: buildEmailBisonExtraFields('emailbison_email_account_reconnected'), + }), + outputs: buildEmailBisonEmailAccountReconnectedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/email_account_removed.ts b/apps/sim/triggers/emailbison/email_account_removed.ts new file mode 100644 index 00000000000..6ea2038c287 --- /dev/null +++ b/apps/sim/triggers/emailbison/email_account_removed.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonEmailAccountRemovedOutputs, + buildEmailBisonExtraFields, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonEmailAccountRemovedTrigger: TriggerConfig = { + id: 'emailbison_email_account_removed', + name: 'Email Bison Email Account Removed', + provider: 'emailbison', + description: 'Trigger when a sender email account is removed from Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_email_account_removed', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Email Account Removed'), + extraFields: buildEmailBisonExtraFields('emailbison_email_account_removed'), + }), + outputs: buildEmailBisonEmailAccountRemovedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/email_bounced.ts b/apps/sim/triggers/emailbison/email_bounced.ts new file mode 100644 index 00000000000..adfee951386 --- /dev/null +++ b/apps/sim/triggers/emailbison/email_bounced.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonEmailBouncedOutputs, + buildEmailBisonExtraFields, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonEmailBouncedTrigger: TriggerConfig = { + id: 'emailbison_email_bounced', + name: 'Email Bison Email Bounced', + provider: 'emailbison', + description: 'Trigger when an Email Bison campaign email bounces', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_email_bounced', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Email Bounced'), + extraFields: buildEmailBisonExtraFields('emailbison_email_bounced'), + }), + outputs: buildEmailBisonEmailBouncedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/email_opened.ts b/apps/sim/triggers/emailbison/email_opened.ts new file mode 100644 index 00000000000..53e7bc9e5f6 --- /dev/null +++ b/apps/sim/triggers/emailbison/email_opened.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonEmailOpenedOutputs, + buildEmailBisonExtraFields, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonEmailOpenedTrigger: TriggerConfig = { + id: 'emailbison_email_opened', + name: 'Email Bison Email Opened', + provider: 'emailbison', + description: 'Trigger when an Email Bison campaign email is opened', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_email_opened', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Email Opened'), + extraFields: buildEmailBisonExtraFields('emailbison_email_opened'), + }), + outputs: buildEmailBisonEmailOpenedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/email_sent.ts b/apps/sim/triggers/emailbison/email_sent.ts new file mode 100644 index 00000000000..3b7bcbabfd4 --- /dev/null +++ b/apps/sim/triggers/emailbison/email_sent.ts @@ -0,0 +1,27 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonEmailSentOutputs, + buildEmailBisonExtraFields, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonEmailSentTrigger: TriggerConfig = { + id: 'emailbison_email_sent', + name: 'Email Bison Email Sent', + provider: 'emailbison', + description: 'Trigger when a campaign email is sent in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_email_sent', + triggerOptions: emailBisonTriggerOptions, + includeDropdown: true, + setupInstructions: emailBisonSetupInstructions('Email Sent'), + extraFields: buildEmailBisonExtraFields('emailbison_email_sent'), + }), + outputs: buildEmailBisonEmailSentOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/index.ts b/apps/sim/triggers/emailbison/index.ts new file mode 100644 index 00000000000..7876e0731c6 --- /dev/null +++ b/apps/sim/triggers/emailbison/index.ts @@ -0,0 +1,17 @@ +export { emailBisonEmailAccountAddedTrigger } from '@/triggers/emailbison/email_account_added' +export { emailBisonEmailAccountDisconnectedTrigger } from '@/triggers/emailbison/email_account_disconnected' +export { emailBisonEmailAccountReconnectedTrigger } from '@/triggers/emailbison/email_account_reconnected' +export { emailBisonEmailAccountRemovedTrigger } from '@/triggers/emailbison/email_account_removed' +export { emailBisonEmailBouncedTrigger } from '@/triggers/emailbison/email_bounced' +export { emailBisonEmailOpenedTrigger } from '@/triggers/emailbison/email_opened' +export { emailBisonEmailSentTrigger } from '@/triggers/emailbison/email_sent' +export { emailBisonLeadFirstContactedTrigger } from '@/triggers/emailbison/lead_first_contacted' +export { emailBisonLeadInterestedTrigger } from '@/triggers/emailbison/lead_interested' +export { emailBisonLeadRepliedTrigger } from '@/triggers/emailbison/lead_replied' +export { emailBisonLeadUnsubscribedTrigger } from '@/triggers/emailbison/lead_unsubscribed' +export { emailBisonManualEmailSentTrigger } from '@/triggers/emailbison/manual_email_sent' +export { emailBisonTagAttachedTrigger } from '@/triggers/emailbison/tag_attached' +export { emailBisonTagRemovedTrigger } from '@/triggers/emailbison/tag_removed' +export { emailBisonUntrackedReplyReceivedTrigger } from '@/triggers/emailbison/untracked_reply_received' +export { emailBisonWarmupDisabledCausingBouncesTrigger } from '@/triggers/emailbison/warmup_disabled_causing_bounces' +export { emailBisonWarmupDisabledReceivingBouncesTrigger } from '@/triggers/emailbison/warmup_disabled_receiving_bounces' diff --git a/apps/sim/triggers/emailbison/lead_first_contacted.ts b/apps/sim/triggers/emailbison/lead_first_contacted.ts new file mode 100644 index 00000000000..406940ce0b0 --- /dev/null +++ b/apps/sim/triggers/emailbison/lead_first_contacted.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonLeadFirstContactedOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonLeadFirstContactedTrigger: TriggerConfig = { + id: 'emailbison_lead_first_contacted', + name: 'Email Bison Contact First Emailed', + provider: 'emailbison', + description: 'Trigger when a contact receives their first campaign email in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_lead_first_contacted', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Contact First Emailed'), + extraFields: buildEmailBisonExtraFields('emailbison_lead_first_contacted'), + }), + outputs: buildEmailBisonLeadFirstContactedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/lead_interested.ts b/apps/sim/triggers/emailbison/lead_interested.ts new file mode 100644 index 00000000000..b4fbdbd5202 --- /dev/null +++ b/apps/sim/triggers/emailbison/lead_interested.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonLeadInterestedOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonLeadInterestedTrigger: TriggerConfig = { + id: 'emailbison_lead_interested', + name: 'Email Bison Contact Interested', + provider: 'emailbison', + description: 'Trigger when a reply is marked interested in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_lead_interested', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Contact Interested'), + extraFields: buildEmailBisonExtraFields('emailbison_lead_interested'), + }), + outputs: buildEmailBisonLeadInterestedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/lead_replied.ts b/apps/sim/triggers/emailbison/lead_replied.ts new file mode 100644 index 00000000000..6b47967e42a --- /dev/null +++ b/apps/sim/triggers/emailbison/lead_replied.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonLeadRepliedOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonLeadRepliedTrigger: TriggerConfig = { + id: 'emailbison_lead_replied', + name: 'Email Bison Contact Replied', + provider: 'emailbison', + description: 'Trigger when a campaign lead replies in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_lead_replied', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Contact Replied'), + extraFields: buildEmailBisonExtraFields('emailbison_lead_replied'), + }), + outputs: buildEmailBisonLeadRepliedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/lead_unsubscribed.ts b/apps/sim/triggers/emailbison/lead_unsubscribed.ts new file mode 100644 index 00000000000..ce7f5f7aef4 --- /dev/null +++ b/apps/sim/triggers/emailbison/lead_unsubscribed.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonLeadUnsubscribedOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonLeadUnsubscribedTrigger: TriggerConfig = { + id: 'emailbison_lead_unsubscribed', + name: 'Email Bison Contact Unsubscribed', + provider: 'emailbison', + description: 'Trigger when a contact unsubscribes in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_lead_unsubscribed', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Contact Unsubscribed'), + extraFields: buildEmailBisonExtraFields('emailbison_lead_unsubscribed'), + }), + outputs: buildEmailBisonLeadUnsubscribedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/manual_email_sent.ts b/apps/sim/triggers/emailbison/manual_email_sent.ts new file mode 100644 index 00000000000..772b88e5c06 --- /dev/null +++ b/apps/sim/triggers/emailbison/manual_email_sent.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonManualEmailSentOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonManualEmailSentTrigger: TriggerConfig = { + id: 'emailbison_manual_email_sent', + name: 'Email Bison Manual Email Sent', + provider: 'emailbison', + description: 'Trigger when a manual email is sent in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_manual_email_sent', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Manual Email Sent'), + extraFields: buildEmailBisonExtraFields('emailbison_manual_email_sent'), + }), + outputs: buildEmailBisonManualEmailSentOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/tag_attached.ts b/apps/sim/triggers/emailbison/tag_attached.ts new file mode 100644 index 00000000000..6d02e97f107 --- /dev/null +++ b/apps/sim/triggers/emailbison/tag_attached.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonTagAttachedOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonTagAttachedTrigger: TriggerConfig = { + id: 'emailbison_tag_attached', + name: 'Email Bison Tag Attached', + provider: 'emailbison', + description: 'Trigger when a custom tag is attached to a taggable in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_tag_attached', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Tag Attached'), + extraFields: buildEmailBisonExtraFields('emailbison_tag_attached'), + }), + outputs: buildEmailBisonTagAttachedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/tag_removed.ts b/apps/sim/triggers/emailbison/tag_removed.ts new file mode 100644 index 00000000000..cd128f8ec09 --- /dev/null +++ b/apps/sim/triggers/emailbison/tag_removed.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonTagRemovedOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonTagRemovedTrigger: TriggerConfig = { + id: 'emailbison_tag_removed', + name: 'Email Bison Tag Removed', + provider: 'emailbison', + description: 'Trigger when a custom tag is removed from a taggable in Email Bison', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_tag_removed', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Tag Removed'), + extraFields: buildEmailBisonExtraFields('emailbison_tag_removed'), + }), + outputs: buildEmailBisonTagRemovedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/untracked_reply_received.ts b/apps/sim/triggers/emailbison/untracked_reply_received.ts new file mode 100644 index 00000000000..6dbe61bd0ba --- /dev/null +++ b/apps/sim/triggers/emailbison/untracked_reply_received.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonUntrackedReplyReceivedOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonUntrackedReplyReceivedTrigger: TriggerConfig = { + id: 'emailbison_untracked_reply_received', + name: 'Email Bison Untracked Reply Received', + provider: 'emailbison', + description: 'Trigger when Email Bison receives a reply not tied to a scheduled campaign email', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_untracked_reply_received', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Untracked Reply Received'), + extraFields: buildEmailBisonExtraFields('emailbison_untracked_reply_received'), + }), + outputs: buildEmailBisonUntrackedReplyReceivedOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/utils.ts b/apps/sim/triggers/emailbison/utils.ts new file mode 100644 index 00000000000..12d9ebbe789 --- /dev/null +++ b/apps/sim/triggers/emailbison/utils.ts @@ -0,0 +1,510 @@ +import type { SubBlockConfig } from '@/blocks/types' +import type { TriggerOutput } from '@/triggers/types' + +export const EMAILBISON_TRIGGER_TO_EVENT_TYPE = { + emailbison_email_sent: 'email_sent', + emailbison_lead_first_contacted: 'lead_first_contacted', + emailbison_lead_replied: 'lead_replied', + emailbison_lead_interested: 'lead_interested', + emailbison_lead_unsubscribed: 'lead_unsubscribed', + emailbison_untracked_reply_received: 'untracked_reply_received', + emailbison_email_opened: 'email_opened', + emailbison_email_bounced: 'email_bounced', + emailbison_email_account_added: 'email_account_added', + emailbison_email_account_removed: 'email_account_removed', + emailbison_email_account_disconnected: 'email_account_disconnected', + emailbison_email_account_reconnected: 'email_account_reconnected', + emailbison_manual_email_sent: 'manual_email_sent', + emailbison_tag_attached: 'tag_attached', + emailbison_tag_removed: 'tag_removed', + emailbison_warmup_disabled_receiving_bounces: 'warmup_disabled_receiving_bounces', + emailbison_warmup_disabled_causing_bounces: 'warmup_disabled_causing_bounces', +} as const + +export const emailBisonTriggerOptions = [ + { label: 'Email Sent', id: 'emailbison_email_sent' }, + { label: 'Contact First Emailed', id: 'emailbison_lead_first_contacted' }, + { label: 'Contact Replied', id: 'emailbison_lead_replied' }, + { label: 'Contact Interested', id: 'emailbison_lead_interested' }, + { label: 'Contact Unsubscribed', id: 'emailbison_lead_unsubscribed' }, + { label: 'Untracked Reply Received', id: 'emailbison_untracked_reply_received' }, + { label: 'Email Opened', id: 'emailbison_email_opened' }, + { label: 'Email Bounced', id: 'emailbison_email_bounced' }, + { label: 'Email Account Added', id: 'emailbison_email_account_added' }, + { label: 'Email Account Removed', id: 'emailbison_email_account_removed' }, + { label: 'Email Account Disconnected', id: 'emailbison_email_account_disconnected' }, + { label: 'Email Account Reconnected', id: 'emailbison_email_account_reconnected' }, + { label: 'Manual Email Sent', id: 'emailbison_manual_email_sent' }, + { label: 'Tag Attached', id: 'emailbison_tag_attached' }, + { label: 'Tag Removed', id: 'emailbison_tag_removed' }, + { + label: 'Warmup Disabled Receiving Bounces', + id: 'emailbison_warmup_disabled_receiving_bounces', + }, + { + label: 'Warmup Disabled Causing Bounces', + id: 'emailbison_warmup_disabled_causing_bounces', + }, +] + +export function emailBisonSetupInstructions(eventType: string): string { + const instructions = [ + 'Create an Email Bison API token in Settings > Developer API.', + 'Enter the Instance URL from Email Bison’s webhook payload, Full API Reference, or exported Postman collection.', + `Click Save Configuration to automatically create an Email Bison webhook for ${eventType}.`, + 'The webhook will be automatically deleted from Email Bison when this trigger is removed.', + ] + + return instructions + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join('') +} + +export function buildEmailBisonExtraFields(triggerId: string): SubBlockConfig[] { + return [ + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your Email Bison API token', + password: true, + required: true, + paramVisibility: 'user-only', + mode: 'trigger', + condition: { field: 'selectedTriggerId', value: triggerId }, + }, + { + id: 'apiBaseUrl', + title: 'Instance URL', + type: 'short-input', + placeholder: 'https://your-emailbison-workspace.com', + required: true, + paramVisibility: 'user-only', + mode: 'trigger', + condition: { field: 'selectedTriggerId', value: triggerId }, + }, + ] +} + +export function buildEmailBisonOutputs(): Record { + return { + eventType: { type: 'string', description: 'Email Bison webhook event type' }, + eventName: { type: 'string', description: 'Human-readable Email Bison event name' }, + instanceUrl: { type: 'string', description: 'Email Bison instance URL' }, + workspaceId: { type: 'number', description: 'Email Bison workspace ID' }, + workspaceName: { type: 'string', description: 'Email Bison workspace name' }, + event: { type: 'json', description: 'Raw Email Bison event metadata object' }, + data: { type: 'json', description: 'Raw Email Bison event data object' }, + } +} + +export function buildEmailBisonEmailSentOutputs(): Record { + return { + ...buildEmailBisonOutputs(), + scheduledEmail: { + id: { type: 'number', description: 'Scheduled email ID' }, + lead_id: { type: 'number', description: 'Lead ID' }, + sequence_step_id: { type: 'number', description: 'Sequence step ID' }, + sequence_step_order: { type: 'number', description: 'Sequence step order' }, + sequence_step_variant: { type: 'number', description: 'Sequence step variant' }, + email_subject: { type: 'string', description: 'Email subject' }, + email_body: { type: 'string', description: 'Email body HTML' }, + status: { type: 'string', description: 'Scheduled email status' }, + scheduled_date_est: { type: 'string', description: 'Scheduled date in EST' }, + scheduled_date_local: { type: 'string', description: 'Scheduled date in local timezone' }, + local_timezone: { type: 'string', description: 'Scheduled email local timezone' }, + sent_at: { type: 'string', description: 'Email sent timestamp' }, + opens: { type: 'number', description: 'Open count' }, + replies: { type: 'number', description: 'Reply count' }, + unique_opens: { type: 'number', description: 'Unique open count' }, + unique_replies: { type: 'number', description: 'Unique reply count' }, + interested: { type: 'string', description: 'Interested status' }, + raw_message_id: { type: 'string', description: 'Raw email message ID' }, + }, + campaignEvent: { + id: { type: 'number', description: 'Campaign event ID' }, + event_type: { type: 'string', description: 'Campaign event type' }, + created_at_local: { type: 'string', description: 'Campaign event local creation timestamp' }, + local_timezone: { type: 'string', description: 'Campaign event local timezone' }, + created_at: { type: 'string', description: 'Campaign event creation timestamp' }, + }, + lead: { + id: { type: 'number', description: 'Lead ID' }, + email: { type: 'string', description: 'Lead email address' }, + first_name: { type: 'string', description: 'Lead first name' }, + last_name: { type: 'string', description: 'Lead last name' }, + status: { type: 'string', description: 'Lead status' }, + title: { type: 'string', description: 'Lead title' }, + company: { type: 'string', description: 'Lead company' }, + custom_variables: { type: 'json', description: 'Lead custom variables' }, + emails_sent: { type: 'number', description: 'Lead emails sent count' }, + opens: { type: 'number', description: 'Lead open count' }, + unique_opens: { type: 'number', description: 'Lead unique open count' }, + replies: { type: 'number', description: 'Lead reply count' }, + unique_replies: { type: 'number', description: 'Lead unique reply count' }, + bounces: { type: 'number', description: 'Lead bounce count' }, + }, + campaign: { + id: { type: 'number', description: 'Campaign ID' }, + name: { type: 'string', description: 'Campaign name' }, + }, + senderEmail: { + id: { type: 'number', description: 'Sender email ID' }, + name: { type: 'string', description: 'Sender email name' }, + email: { type: 'string', description: 'Sender email address' }, + status: { type: 'string', description: 'Sender email status' }, + account_type: { type: 'string', description: 'Sender email connection type' }, + daily_limit: { type: 'number', description: 'Sender email daily limit' }, + emails_sent: { type: 'number', description: 'Sender email sent count' }, + replied: { type: 'number', description: 'Sender email replied count' }, + opened: { type: 'number', description: 'Sender email opened count' }, + unsubscribed: { type: 'number', description: 'Sender email unsubscribed count' }, + bounced: { type: 'number', description: 'Sender email bounced count' }, + unique_replies: { type: 'number', description: 'Sender email unique reply count' }, + unique_opens: { type: 'number', description: 'Sender email unique open count' }, + total_leads_contacted: { type: 'number', description: 'Sender email total leads contacted' }, + interested: { type: 'number', description: 'Sender email interested count' }, + created_at: { type: 'string', description: 'Sender email creation timestamp' }, + updated_at: { type: 'string', description: 'Sender email update timestamp' }, + }, + } +} + +export function buildEmailBisonLeadFirstContactedOutputs(): Record { + return buildEmailBisonEmailSentOutputs() +} + +export function buildEmailBisonLeadUnsubscribedOutputs(): Record { + return buildEmailBisonEmailSentOutputs() +} + +export function buildEmailBisonEmailOpenedOutputs(): Record { + return buildEmailBisonEmailSentOutputs() +} + +export function buildEmailBisonLeadRepliedOutputs(): Record { + return { + ...buildEmailBisonOutputs(), + reply: { + id: { type: 'number', description: 'Reply ID' }, + uuid: { type: 'string', description: 'Reply UUID' }, + email_subject: { type: 'string', description: 'Reply email subject' }, + interested: { type: 'boolean', description: 'Whether the reply is marked interested' }, + automated_reply: { type: 'boolean', description: 'Whether the reply is automated' }, + html_body: { type: 'string', description: 'Reply HTML body' }, + text_body: { type: 'string', description: 'Reply plain text body' }, + raw_body: { type: 'string', description: 'Raw MIME reply body' }, + headers: { type: 'string', description: 'Encoded raw email headers' }, + date_received: { type: 'string', description: 'Reply received timestamp' }, + from_name: { type: 'string', description: 'Reply sender name' }, + from_email_address: { type: 'string', description: 'Reply sender email address' }, + primary_to_email_address: { type: 'string', description: 'Primary recipient email address' }, + to: { type: 'json', description: 'Reply To recipients' }, + cc: { type: 'json', description: 'Reply CC recipients' }, + bcc: { type: 'json', description: 'Reply BCC recipients' }, + parent_id: { type: 'number', description: 'Parent reply ID' }, + reply_type: { type: 'string', description: 'Reply type' }, + folder: { type: 'string', description: 'Reply folder' }, + raw_message_id: { type: 'string', description: 'Raw email message ID' }, + created_at: { type: 'string', description: 'Reply creation timestamp' }, + updated_at: { type: 'string', description: 'Reply update timestamp' }, + attachments: { type: 'json', description: 'Reply attachments' }, + }, + campaignEvent: { + id: { type: 'number', description: 'Campaign event ID' }, + event_type: { type: 'string', description: 'Campaign event type' }, + created_at_local: { type: 'string', description: 'Campaign event local creation timestamp' }, + local_timezone: { type: 'string', description: 'Campaign event local timezone' }, + created_at: { type: 'string', description: 'Campaign event creation timestamp' }, + }, + lead: { + id: { type: 'number', description: 'Lead ID' }, + email: { type: 'string', description: 'Lead email address' }, + first_name: { type: 'string', description: 'Lead first name' }, + last_name: { type: 'string', description: 'Lead last name' }, + status: { type: 'string', description: 'Lead status' }, + title: { type: 'string', description: 'Lead title' }, + company: { type: 'string', description: 'Lead company' }, + custom_variables: { type: 'json', description: 'Lead custom variables' }, + emails_sent: { type: 'number', description: 'Lead emails sent count' }, + opens: { type: 'number', description: 'Lead open count' }, + unique_opens: { type: 'number', description: 'Lead unique open count' }, + replies: { type: 'number', description: 'Lead reply count' }, + unique_replies: { type: 'number', description: 'Lead unique reply count' }, + bounces: { type: 'number', description: 'Lead bounce count' }, + }, + campaign: { + id: { type: 'number', description: 'Campaign ID' }, + name: { type: 'string', description: 'Campaign name' }, + }, + scheduledEmail: { + id: { type: 'number', description: 'Scheduled email ID' }, + sequence_step_id: { type: 'number', description: 'Sequence step ID' }, + sequence_step_order: { type: 'number', description: 'Sequence step order' }, + sequence_step_variant: { type: 'number', description: 'Sequence step variant' }, + status: { type: 'string', description: 'Scheduled email status' }, + scheduled_date_est: { type: 'string', description: 'Scheduled date in EST' }, + scheduled_date_local: { type: 'string', description: 'Scheduled date in local timezone' }, + local_timezone: { type: 'string', description: 'Scheduled email local timezone' }, + sent_at: { type: 'string', description: 'Email sent timestamp' }, + opens: { type: 'number', description: 'Open count' }, + replies: { type: 'number', description: 'Reply count' }, + unique_opens: { type: 'number', description: 'Unique open count' }, + unique_replies: { type: 'number', description: 'Unique reply count' }, + interested: { type: 'string', description: 'Interested status' }, + raw_message_id: { type: 'string', description: 'Raw email message ID' }, + }, + senderEmail: { + id: { type: 'number', description: 'Sender email ID' }, + name: { type: 'string', description: 'Sender email name' }, + email: { type: 'string', description: 'Sender email address' }, + status: { type: 'string', description: 'Sender email status' }, + account_type: { type: 'string', description: 'Sender email connection type' }, + daily_limit: { type: 'number', description: 'Sender email daily limit' }, + emails_sent: { type: 'number', description: 'Sender email sent count' }, + replied: { type: 'number', description: 'Sender email replied count' }, + opened: { type: 'number', description: 'Sender email opened count' }, + unsubscribed: { type: 'number', description: 'Sender email unsubscribed count' }, + bounced: { type: 'number', description: 'Sender email bounced count' }, + unique_replies: { type: 'number', description: 'Sender email unique reply count' }, + unique_opens: { type: 'number', description: 'Sender email unique open count' }, + total_leads_contacted: { type: 'number', description: 'Sender email total leads contacted' }, + interested: { type: 'number', description: 'Sender email interested count' }, + created_at: { type: 'string', description: 'Sender email creation timestamp' }, + updated_at: { type: 'string', description: 'Sender email update timestamp' }, + }, + } +} + +export function buildEmailBisonLeadInterestedOutputs(): Record { + return buildEmailBisonLeadRepliedOutputs() +} + +export function buildEmailBisonEmailBouncedOutputs(): Record { + return buildEmailBisonLeadRepliedOutputs() +} + +export function buildEmailBisonManualEmailSentOutputs(): Record { + return { + ...buildEmailBisonOutputs(), + reply: { + id: { type: 'number', description: 'Reply ID' }, + email_subject: { type: 'string', description: 'Reply email subject' }, + interested: { type: 'boolean', description: 'Whether the reply is marked interested' }, + automated_reply: { type: 'boolean', description: 'Whether the reply is automated' }, + html_body: { type: 'string', description: 'Reply HTML body' }, + text_body: { type: 'string', description: 'Reply plain text body' }, + raw_body: { type: 'string', description: 'Raw MIME reply body' }, + headers: { type: 'string', description: 'Encoded raw email headers' }, + date_received: { type: 'string', description: 'Reply received timestamp' }, + reply_type: { type: 'string', description: 'Reply type' }, + from_name: { type: 'string', description: 'Reply sender name' }, + from_email_address: { type: 'string', description: 'Reply sender email address' }, + primary_to_email_address: { type: 'string', description: 'Primary recipient email address' }, + to: { type: 'json', description: 'Reply To recipients' }, + cc: { type: 'json', description: 'Reply CC recipients' }, + bcc: { type: 'json', description: 'Reply BCC recipients' }, + parent_id: { type: 'json', description: 'Parent reply ID' }, + folder: { type: 'string', description: 'Reply folder' }, + raw_message_id: { type: 'string', description: 'Raw email message ID' }, + created_at: { type: 'string', description: 'Reply creation timestamp' }, + updated_at: { type: 'string', description: 'Reply update timestamp' }, + attachments: { type: 'json', description: 'Reply attachments' }, + }, + lead: { + id: { type: 'number', description: 'Lead ID' }, + email: { type: 'string', description: 'Lead email address' }, + first_name: { type: 'string', description: 'Lead first name' }, + last_name: { type: 'string', description: 'Lead last name' }, + status: { type: 'string', description: 'Lead status' }, + title: { type: 'string', description: 'Lead title' }, + company: { type: 'string', description: 'Lead company' }, + custom_variables: { type: 'json', description: 'Lead custom variables' }, + emails_sent: { type: 'number', description: 'Lead emails sent count' }, + opens: { type: 'number', description: 'Lead open count' }, + unique_opens: { type: 'number', description: 'Lead unique open count' }, + replies: { type: 'number', description: 'Lead reply count' }, + unique_replies: { type: 'number', description: 'Lead unique reply count' }, + bounces: { type: 'number', description: 'Lead bounce count' }, + }, + campaign: { + id: { type: 'number', description: 'Campaign ID' }, + name: { type: 'string', description: 'Campaign name' }, + }, + scheduledEmail: { + id: { type: 'number', description: 'Scheduled email ID' }, + sequence_step_id: { type: 'number', description: 'Sequence step ID' }, + sequence_step_order: { type: 'number', description: 'Sequence step order' }, + sequence_step_variant: { type: 'number', description: 'Sequence step variant' }, + status: { type: 'string', description: 'Scheduled email status' }, + scheduled_date_est: { type: 'string', description: 'Scheduled date in EST' }, + scheduled_date_local: { type: 'string', description: 'Scheduled date in local timezone' }, + local_timezone: { type: 'string', description: 'Scheduled email local timezone' }, + sent_at: { type: 'string', description: 'Email sent timestamp' }, + opens: { type: 'number', description: 'Open count' }, + replies: { type: 'number', description: 'Reply count' }, + unique_opens: { type: 'number', description: 'Unique open count' }, + unique_replies: { type: 'number', description: 'Unique reply count' }, + interested: { type: 'json', description: 'Interested status' }, + raw_message_id: { type: 'string', description: 'Raw email message ID' }, + }, + senderEmail: { + id: { type: 'number', description: 'Sender email ID' }, + name: { type: 'string', description: 'Sender email name' }, + email: { type: 'string', description: 'Sender email address' }, + status: { type: 'string', description: 'Sender email status' }, + account_type: { type: 'string', description: 'Sender email connection type' }, + daily_limit: { type: 'number', description: 'Sender email daily limit' }, + emails_sent: { type: 'number', description: 'Sender email sent count' }, + replied: { type: 'number', description: 'Sender email replied count' }, + opened: { type: 'number', description: 'Sender email opened count' }, + unsubscribed: { type: 'number', description: 'Sender email unsubscribed count' }, + bounced: { type: 'number', description: 'Sender email bounced count' }, + unique_replies: { type: 'number', description: 'Sender email unique reply count' }, + unique_opens: { type: 'number', description: 'Sender email unique open count' }, + total_leads_contacted: { type: 'number', description: 'Sender email total leads contacted' }, + interested: { type: 'number', description: 'Sender email interested count' }, + created_at: { type: 'string', description: 'Sender email creation timestamp' }, + updated_at: { type: 'string', description: 'Sender email update timestamp' }, + }, + } +} + +export function buildEmailBisonTagAttachedOutputs(): Record { + return { + ...buildEmailBisonOutputs(), + tagId: { type: 'number', description: 'Email Bison tag ID' }, + tagName: { type: 'string', description: 'Email Bison tag name' }, + taggableId: { type: 'number', description: 'ID of the tagged resource' }, + taggableType: { type: 'string', description: 'Type of the tagged resource' }, + } +} + +export function buildEmailBisonTagRemovedOutputs(): Record { + return buildEmailBisonTagAttachedOutputs() +} + +export function buildEmailBisonEmailAccountAddedOutputs(): Record { + return { + ...buildEmailBisonOutputs(), + senderEmail: { + id: { type: 'number', description: 'Sender email ID' }, + name: { type: 'string', description: 'Sender email name' }, + email: { type: 'string', description: 'Sender email address' }, + status: { type: 'string', description: 'Sender email status' }, + account_type: { type: 'string', description: 'Sender email connection type' }, + daily_limit: { type: 'number', description: 'Sender email daily limit' }, + emails_sent: { type: 'number', description: 'Sender email sent count' }, + replied: { type: 'number', description: 'Sender email replied count' }, + opened: { type: 'number', description: 'Sender email opened count' }, + unsubscribed: { type: 'number', description: 'Sender email unsubscribed count' }, + bounced: { type: 'number', description: 'Sender email bounced count' }, + unique_replies: { type: 'number', description: 'Sender email unique reply count' }, + unique_opens: { type: 'number', description: 'Sender email unique open count' }, + total_leads_contacted: { type: 'number', description: 'Sender email total leads contacted' }, + interested: { type: 'number', description: 'Sender email interested count' }, + created_at: { type: 'string', description: 'Sender email creation timestamp' }, + updated_at: { type: 'string', description: 'Sender email update timestamp' }, + }, + } +} + +export function buildEmailBisonEmailAccountRemovedOutputs(): Record { + return buildEmailBisonEmailAccountAddedOutputs() +} + +export function buildEmailBisonEmailAccountDisconnectedOutputs(): Record { + return buildEmailBisonEmailAccountAddedOutputs() +} + +export function buildEmailBisonEmailAccountReconnectedOutputs(): Record { + return buildEmailBisonEmailAccountAddedOutputs() +} + +export function buildEmailBisonWarmupDisabledReceivingBouncesOutputs(): Record< + string, + TriggerOutput +> { + return buildEmailBisonEmailAccountAddedOutputs() +} + +export function buildEmailBisonWarmupDisabledCausingBouncesOutputs(): Record< + string, + TriggerOutput +> { + return buildEmailBisonEmailAccountAddedOutputs() +} + +export function buildEmailBisonUntrackedReplyReceivedOutputs(): Record { + return { + ...buildEmailBisonOutputs(), + reply: { + id: { type: 'number', description: 'Reply ID' }, + uuid: { type: 'string', description: 'Reply UUID' }, + email_subject: { type: 'string', description: 'Reply email subject' }, + interested: { type: 'boolean', description: 'Whether the reply is marked interested' }, + automated_reply: { type: 'boolean', description: 'Whether the reply is automated' }, + html_body: { type: 'string', description: 'Reply HTML body' }, + text_body: { type: 'string', description: 'Reply plain text body' }, + raw_body: { type: 'string', description: 'Raw MIME reply body' }, + headers: { type: 'string', description: 'Encoded raw email headers' }, + date_received: { type: 'string', description: 'Reply received timestamp' }, + from_name: { type: 'string', description: 'Reply sender name' }, + from_email_address: { type: 'string', description: 'Reply sender email address' }, + primary_to_email_address: { type: 'string', description: 'Primary recipient email address' }, + to: { type: 'json', description: 'Reply To recipients' }, + cc: { type: 'json', description: 'Reply CC recipients' }, + bcc: { type: 'json', description: 'Reply BCC recipients' }, + parent_id: { type: 'number', description: 'Parent reply ID' }, + reply_type: { type: 'string', description: 'Reply type' }, + folder: { type: 'string', description: 'Reply folder' }, + raw_message_id: { type: 'string', description: 'Raw email message ID' }, + created_at: { type: 'string', description: 'Reply creation timestamp' }, + updated_at: { type: 'string', description: 'Reply update timestamp' }, + attachments: { type: 'json', description: 'Reply attachments' }, + }, + senderEmail: { + id: { type: 'number', description: 'Sender email ID' }, + name: { type: 'string', description: 'Sender email name' }, + email: { type: 'string', description: 'Sender email address' }, + status: { type: 'string', description: 'Sender email status' }, + account_type: { type: 'string', description: 'Sender email connection type' }, + daily_limit: { type: 'number', description: 'Sender email daily limit' }, + emails_sent: { type: 'number', description: 'Sender email sent count' }, + replied: { type: 'number', description: 'Sender email replied count' }, + opened: { type: 'number', description: 'Sender email opened count' }, + unsubscribed: { type: 'number', description: 'Sender email unsubscribed count' }, + bounced: { type: 'number', description: 'Sender email bounced count' }, + unique_replies: { type: 'number', description: 'Sender email unique reply count' }, + unique_opens: { type: 'number', description: 'Sender email unique open count' }, + total_leads_contacted: { type: 'number', description: 'Sender email total leads contacted' }, + interested: { type: 'number', description: 'Sender email interested count' }, + created_at: { type: 'string', description: 'Sender email creation timestamp' }, + updated_at: { type: 'string', description: 'Sender email update timestamp' }, + }, + } +} + +export function getEmailBisonEventTypeForTrigger(triggerId: string): string | undefined { + return EMAILBISON_TRIGGER_TO_EVENT_TYPE[ + triggerId as keyof typeof EMAILBISON_TRIGGER_TO_EVENT_TYPE + ] +} + +export function isEmailBisonEventMatch(triggerId: string, body: Record): boolean { + const expectedEventType = getEmailBisonEventTypeForTrigger(triggerId) + if (!expectedEventType) return false + + const event = body.event + if (!isRecord(event)) return false + + const actualEventType = event.type + return typeof actualEventType === 'string' && actualEventType.toLowerCase() === expectedEventType +} + +function isRecord(value: unknown): value is Record { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value) +} diff --git a/apps/sim/triggers/emailbison/warmup_disabled_causing_bounces.ts b/apps/sim/triggers/emailbison/warmup_disabled_causing_bounces.ts new file mode 100644 index 00000000000..341138bb833 --- /dev/null +++ b/apps/sim/triggers/emailbison/warmup_disabled_causing_bounces.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonWarmupDisabledCausingBouncesOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonWarmupDisabledCausingBouncesTrigger: TriggerConfig = { + id: 'emailbison_warmup_disabled_causing_bounces', + name: 'Email Bison Warmup Disabled Causing Bounces', + provider: 'emailbison', + description: 'Trigger when warmup is disabled for a sender email causing too many bounces', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_warmup_disabled_causing_bounces', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Warmup Disabled Causing Bounces'), + extraFields: buildEmailBisonExtraFields('emailbison_warmup_disabled_causing_bounces'), + }), + outputs: buildEmailBisonWarmupDisabledCausingBouncesOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/emailbison/warmup_disabled_receiving_bounces.ts b/apps/sim/triggers/emailbison/warmup_disabled_receiving_bounces.ts new file mode 100644 index 00000000000..6ecfcf2b4e1 --- /dev/null +++ b/apps/sim/triggers/emailbison/warmup_disabled_receiving_bounces.ts @@ -0,0 +1,26 @@ +import { EmailBisonIcon } from '@/components/icons' +import { buildTriggerSubBlocks } from '@/triggers' +import { + buildEmailBisonExtraFields, + buildEmailBisonWarmupDisabledReceivingBouncesOutputs, + emailBisonSetupInstructions, + emailBisonTriggerOptions, +} from '@/triggers/emailbison/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const emailBisonWarmupDisabledReceivingBouncesTrigger: TriggerConfig = { + id: 'emailbison_warmup_disabled_receiving_bounces', + name: 'Email Bison Warmup Disabled Receiving Bounces', + provider: 'emailbison', + description: 'Trigger when warmup is disabled for a sender email receiving too many bounces', + version: '1.0.0', + icon: EmailBisonIcon, + subBlocks: buildTriggerSubBlocks({ + triggerId: 'emailbison_warmup_disabled_receiving_bounces', + triggerOptions: emailBisonTriggerOptions, + setupInstructions: emailBisonSetupInstructions('Warmup Disabled Receiving Bounces'), + extraFields: buildEmailBisonExtraFields('emailbison_warmup_disabled_receiving_bounces'), + }), + outputs: buildEmailBisonWarmupDisabledReceivingBouncesOutputs(), + webhook: { method: 'POST', headers: { 'Content-Type': 'application/json' } }, +} diff --git a/apps/sim/triggers/registry.ts b/apps/sim/triggers/registry.ts index b32941173ab..b1d747f529f 100644 --- a/apps/sim/triggers/registry.ts +++ b/apps/sim/triggers/registry.ts @@ -78,6 +78,25 @@ import { confluenceUserCreatedTrigger, confluenceWebhookTrigger, } from '@/triggers/confluence' +import { + emailBisonEmailAccountAddedTrigger, + emailBisonEmailAccountDisconnectedTrigger, + emailBisonEmailAccountReconnectedTrigger, + emailBisonEmailAccountRemovedTrigger, + emailBisonEmailBouncedTrigger, + emailBisonEmailOpenedTrigger, + emailBisonEmailSentTrigger, + emailBisonLeadFirstContactedTrigger, + emailBisonLeadInterestedTrigger, + emailBisonLeadRepliedTrigger, + emailBisonLeadUnsubscribedTrigger, + emailBisonManualEmailSentTrigger, + emailBisonTagAttachedTrigger, + emailBisonTagRemovedTrigger, + emailBisonUntrackedReplyReceivedTrigger, + emailBisonWarmupDisabledCausingBouncesTrigger, + emailBisonWarmupDisabledReceivingBouncesTrigger, +} from '@/triggers/emailbison' import { fathomNewMeetingTrigger, fathomWebhookTrigger } from '@/triggers/fathom' import { firefliesTranscriptionCompleteTrigger } from '@/triggers/fireflies' import { genericWebhookTrigger } from '@/triggers/generic' @@ -379,6 +398,23 @@ export const TRIGGER_REGISTRY: TriggerRegistry = { confluence_space_removed: confluenceSpaceRemovedTrigger, confluence_page_permissions_updated: confluencePagePermissionsUpdatedTrigger, confluence_user_created: confluenceUserCreatedTrigger, + emailbison_email_sent: emailBisonEmailSentTrigger, + emailbison_lead_first_contacted: emailBisonLeadFirstContactedTrigger, + emailbison_lead_replied: emailBisonLeadRepliedTrigger, + emailbison_lead_interested: emailBisonLeadInterestedTrigger, + emailbison_lead_unsubscribed: emailBisonLeadUnsubscribedTrigger, + emailbison_untracked_reply_received: emailBisonUntrackedReplyReceivedTrigger, + emailbison_email_opened: emailBisonEmailOpenedTrigger, + emailbison_email_bounced: emailBisonEmailBouncedTrigger, + emailbison_email_account_added: emailBisonEmailAccountAddedTrigger, + emailbison_email_account_removed: emailBisonEmailAccountRemovedTrigger, + emailbison_email_account_disconnected: emailBisonEmailAccountDisconnectedTrigger, + emailbison_email_account_reconnected: emailBisonEmailAccountReconnectedTrigger, + emailbison_manual_email_sent: emailBisonManualEmailSentTrigger, + emailbison_tag_attached: emailBisonTagAttachedTrigger, + emailbison_tag_removed: emailBisonTagRemovedTrigger, + emailbison_warmup_disabled_receiving_bounces: emailBisonWarmupDisabledReceivingBouncesTrigger, + emailbison_warmup_disabled_causing_bounces: emailBisonWarmupDisabledCausingBouncesTrigger, generic_webhook: genericWebhookTrigger, greenhouse_candidate_hired: greenhouseCandidateHiredTrigger, greenhouse_new_application: greenhouseNewApplicationTrigger, From 5950a963939e34f9a0bc5ee52d050c3a58da11cb Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 6 May 2026 12:19:34 -0700 Subject: [PATCH 5/7] address comments --- .../tools/emailbison/update_campaign_status.ts | 15 +++++++++++++-- apps/sim/tools/sharepoint/read_page.ts | 2 +- apps/sim/tools/sharepoint/utils.ts | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/sim/tools/emailbison/update_campaign_status.ts b/apps/sim/tools/emailbison/update_campaign_status.ts index 951eb556fbf..4557e47ed8e 100644 --- a/apps/sim/tools/emailbison/update_campaign_status.ts +++ b/apps/sim/tools/emailbison/update_campaign_status.ts @@ -12,6 +12,8 @@ import { } from '@/tools/emailbison/utils' import type { ToolConfig } from '@/tools/types' +const CAMPAIGN_STATUS_ACTIONS = new Set(['pause', 'resume', 'archive']) + export const updateCampaignStatusTool: ToolConfig< EmailBisonCampaignStatusParams, EmailBisonCampaignResponse @@ -36,8 +38,17 @@ export const updateCampaignStatusTool: ToolConfig< }, }, request: { - url: (params) => - emailBisonUrl(`/api/campaigns/${params.campaignId}/${params.action}`, {}, params.apiBaseUrl), + url: (params) => { + if (!CAMPAIGN_STATUS_ACTIONS.has(params.action)) { + throw new Error('Email Bison campaign status action must be pause, resume, or archive') + } + + return emailBisonUrl( + `/api/campaigns/${params.campaignId}/${params.action}`, + {}, + params.apiBaseUrl + ) + }, method: 'PATCH', headers: emailBisonHeaders, }, diff --git a/apps/sim/tools/sharepoint/read_page.ts b/apps/sim/tools/sharepoint/read_page.ts index 00204dc782d..554f747af0b 100644 --- a/apps/sim/tools/sharepoint/read_page.ts +++ b/apps/sim/tools/sharepoint/read_page.ts @@ -255,7 +255,7 @@ export const readPageTool: ToolConfig) + const nextPageUrl = getGraphNextPageUrl(data) logger.info('Fetching content for all pages', { totalPages: data.value.length, diff --git a/apps/sim/tools/sharepoint/utils.ts b/apps/sim/tools/sharepoint/utils.ts index 266821cbb67..200e67156eb 100644 --- a/apps/sim/tools/sharepoint/utils.ts +++ b/apps/sim/tools/sharepoint/utils.ts @@ -13,8 +13,8 @@ export function escapeODataString(value: string): string { return value.replace(/'/g, "''") } -export function getGraphNextPageUrl(data: Record): string | undefined { - const nextLink = data['@odata.nextLink'] +export function getGraphNextPageUrl(data: object): string | undefined { + const nextLink = (data as Record)['@odata.nextLink'] return typeof nextLink === 'string' ? nextLink : undefined } From 2c35579387969336d9a7f356c10f2ffbf22770a4 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 6 May 2026 12:25:25 -0700 Subject: [PATCH 6/7] fix tests --- apps/sim/blocks/blocks/sharepoint.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/sim/blocks/blocks/sharepoint.ts b/apps/sim/blocks/blocks/sharepoint.ts index fe3879920eb..8ce312ebe94 100644 --- a/apps/sim/blocks/blocks/sharepoint.ts +++ b/apps/sim/blocks/blocks/sharepoint.ts @@ -918,8 +918,6 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, columnDefinitions, listItemFields, files, - uploadFiles, - fileRefs, maxPages, driveId, ...rest @@ -945,7 +943,7 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, } } - const normalizedFiles = normalizeFileInput(files || uploadFiles || fileRefs) + const normalizedFiles = normalizeFileInput(files) const result: Record = { ...rest, oauthCredential, From c3808b1827f30cb9b258a93b8cd074691c6e933e Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 6 May 2026 12:33:08 -0700 Subject: [PATCH 7/7] error on partial upload failures --- .../app/api/tools/sharepoint/upload/route.ts | 67 ++++++++++++------- apps/sim/blocks/blocks/sharepoint.ts | 9 +++ apps/sim/tools/sharepoint/types.ts | 16 +++++ apps/sim/tools/sharepoint/upload_file.ts | 47 +++++++++++-- 4 files changed, 109 insertions(+), 30 deletions(-) diff --git a/apps/sim/app/api/tools/sharepoint/upload/route.ts b/apps/sim/app/api/tools/sharepoint/upload/route.ts index 429a8a24d08..556de6d4225 100644 --- a/apps/sim/app/api/tools/sharepoint/upload/route.ts +++ b/apps/sim/app/api/tools/sharepoint/upload/route.ts @@ -10,10 +10,12 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import type { MicrosoftGraphDriveItem } from '@/tools/onedrive/types' +import type { SharepointSkippedFile, SharepointUploadError } from '@/tools/sharepoint/types' export const dynamic = 'force-dynamic' const logger = createLogger('SharepointUploadAPI') +const MAX_SHAREPOINT_UPLOAD_BYTES = 250 * 1024 * 1024 export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() @@ -76,6 +78,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const siteId = validatedData.siteId.trim() || 'root' const driveId = validatedData.driveId?.trim() || null const uploadedFiles: MicrosoftGraphDriveItem[] = [] + const skippedFiles: SharepointSkippedFile[] = [] + const errors: SharepointUploadError[] = [] for (const userFile of userFiles) { logger.info(`[${requestId}] Uploading file: ${userFile.name}`) @@ -87,10 +91,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const fileSizeMB = buffer.length / (1024 * 1024) - if (fileSizeMB > 250) { + if (buffer.length > MAX_SHAREPOINT_UPLOAD_BYTES) { logger.warn( `[${requestId}] File ${fileName} is ${fileSizeMB.toFixed(2)}MB, exceeds 250MB limit` ) + skippedFiles.push({ + name: fileName, + size: buffer.length, + limit: MAX_SHAREPOINT_UPLOAD_BYTES, + reason: 'File exceeds the 250 MB Microsoft Graph small upload limit', + }) continue } @@ -155,13 +165,12 @@ export const POST = withRouteHandler(async (request: NextRequest) => { error?: { message?: string } } logger.error(`[${requestId}] Failed to replace file ${fileName}:`, replaceErrorData) - return NextResponse.json( - { - success: false, - error: replaceErrorData.error?.message || `Failed to replace file: ${fileName}`, - }, - { status: replaceResponse.status } - ) + errors.push({ + name: fileName, + status: replaceResponse.status, + error: replaceErrorData.error?.message || `Failed to replace file: ${fileName}`, + }) + continue } const replaceData = (await replaceResponse.json()) as { @@ -185,15 +194,14 @@ export const POST = withRouteHandler(async (request: NextRequest) => { continue } - return NextResponse.json( - { - success: false, - error: - (errorData as { error?: { message?: string } }).error?.message || - `Failed to upload file: ${fileName}`, - }, - { status: uploadResponse.status } - ) + errors.push({ + name: fileName, + status: uploadResponse.status, + error: + (errorData as { error?: { message?: string } }).error?.message || + `Failed to upload file: ${fileName}`, + }) + continue } const uploadData = (await uploadResponse.json()) as MicrosoftGraphDriveItem @@ -210,22 +218,33 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } if (uploadedFiles.length === 0) { - return NextResponse.json( - { - success: false, - error: 'No files were uploaded successfully', + return NextResponse.json({ + success: false, + error: 'No files were uploaded successfully', + output: { + uploadedFiles, + fileCount: 0, + skippedFiles, + skippedCount: skippedFiles.length, + errors, }, - { status: 400 } - ) + }) } - logger.info(`[${requestId}] Successfully uploaded ${uploadedFiles.length} file(s)`) + logger.info(`[${requestId}] Completed SharePoint upload`, { + uploadedCount: uploadedFiles.length, + skippedCount: skippedFiles.length, + errorCount: errors.length, + }) return NextResponse.json({ success: true, output: { uploadedFiles, fileCount: uploadedFiles.length, + skippedFiles, + skippedCount: skippedFiles.length, + errors, }, }) } catch (error) { diff --git a/apps/sim/blocks/blocks/sharepoint.ts b/apps/sim/blocks/blocks/sharepoint.ts index 8ce312ebe94..b881c46e24a 100644 --- a/apps/sim/blocks/blocks/sharepoint.ts +++ b/apps/sim/blocks/blocks/sharepoint.ts @@ -1033,6 +1033,15 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, description: 'Array of uploaded file objects with id, name, webUrl, size', }, fileCount: { type: 'number', description: 'Number of files uploaded' }, + skippedFiles: { + type: 'json', + description: 'Array of skipped upload files (name, size, limit, reason)', + }, + skippedCount: { type: 'number', description: 'Number of files skipped' }, + errors: { + type: 'json', + description: 'Array of per-file upload errors (name, error, status)', + }, nextPageUrl: { type: 'string', description: 'Microsoft Graph @odata.nextLink URL for the next page of results', diff --git a/apps/sim/tools/sharepoint/types.ts b/apps/sim/tools/sharepoint/types.ts index c68cc4a0bad..c6b499257d3 100644 --- a/apps/sim/tools/sharepoint/types.ts +++ b/apps/sim/tools/sharepoint/types.ts @@ -285,9 +285,25 @@ export interface SharepointUploadedFile { lastModifiedDateTime?: string } +export interface SharepointSkippedFile { + name: string + size: number + limit: number + reason: string +} + +export interface SharepointUploadError { + name: string + error: string + status?: number +} + export interface SharepointUploadFileResponse extends ToolResponse { output: { uploadedFiles: SharepointUploadedFile[] fileCount: number + skippedFiles?: SharepointSkippedFile[] + skippedCount?: number + errors?: SharepointUploadError[] } } diff --git a/apps/sim/tools/sharepoint/upload_file.ts b/apps/sim/tools/sharepoint/upload_file.ts index c4c1b72cf1f..94626a82ee6 100644 --- a/apps/sim/tools/sharepoint/upload_file.ts +++ b/apps/sim/tools/sharepoint/upload_file.ts @@ -74,15 +74,17 @@ export const uploadFileTool: ToolConfig { const data = await response.json() - if (!data.success) { - throw new Error(data.error || 'Failed to upload files to SharePoint') - } + const output = data.output ?? {} return { - success: true, + success: Boolean(data.success), output: { - uploadedFiles: data.output.uploadedFiles, - fileCount: data.output.fileCount, + uploadedFiles: output.uploadedFiles ?? [], + fileCount: output.fileCount ?? 0, + skippedFiles: output.skippedFiles ?? [], + skippedCount: output.skippedCount ?? 0, + errors: output.errors ?? [], }, + error: data.success ? undefined : data.error || 'Failed to upload files to SharePoint', } }, @@ -106,5 +108,38 @@ export const uploadFileTool: ToolConfig