feat(emailbison): block, tools, sharepoint v2 block with cleaner code (#4470)

* feat(emailbison): block, tools

* type improvments

* typecheck issue

* add email bison trigger, cleanup sharepoint block

* address comments

* fix tests

* error on partial upload failures
This commit is contained in:
Vikhyath Mondreti
2026-05-06 12:44:17 -07:00
committed by GitHub
parent bfd0f46119
commit 79ffccc140
65 changed files with 6265 additions and 252 deletions
+11
View File
@@ -415,6 +415,17 @@ export function MailIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function EmailBisonIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='-66 -2 88 64' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M0 0 C1 2.3 2 4.6 3 7 C7 8.8 7 8.8 11.7 9.9 C13.2 10.3 14.8 10.7 16.4 11.2 C17.6 11.4 18.8 11.7 20 12 C21.5 38 21.5 38 15.8 45.1 C11 49 11 49 7 50 C5.3 49.3 3.7 48.7 2 48 C2 46 2 44 2 42 C0.7 42.3 -0.6 42.7 -2 43 C-1 49.1 0.8 54.3 3 60 C-1 61 -1 61 -7 58 C-7 56.3 -7 54.7 -7 53 C-7.7 54 -8.3 55 -9 56 C-9.7 56.8 -9.7 56.8 -13 61 C-18 60 -18 60 -21 57 C-21.2 56.1 -21.2 56.1 -22 51.3 C-26.1 42.7 -31.1 41.6 -39.7 38.3 C-44 37 -44 37 -47 37 C-53.6 45.2 -56 50.5 -55 61 C-57 60.7 -59 60.3 -61 60 C-65.9 51.1 -63 42.4 -61 33 C-62 34 -63 35 -64 36 C-64.7 36 -65.3 36 -66 36 C-64.4 29.7 -62.6 24 -60 18 C-56.3 16 -52.5 14 -48.6 12.2 C-44 10 -44 10 -39.6 7.3 C-26.4 -0.8 -15.2 -1.1 0 0 Z M-61 27 C-61.3 28 -61.7 29 -62 30 C-61.3 30 -60.7 30 -60 30 C-60.3 29 -60.7 28 -61 27 Z M-7 50 C-6 52 -6 52 -6 52 Z'
fill='currentColor'
/>
</svg>
)
}
export function MailServerIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
+3
View File
@@ -51,6 +51,7 @@ import {
DynamoDBIcon,
ElasticsearchIcon,
ElevenLabsIcon,
EmailBisonIcon,
EnrichSoIcon,
EvernoteIcon,
ExaAIIcon,
@@ -250,6 +251,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
dynamodb: DynamoDBIcon,
elasticsearch: ElasticsearchIcon,
elevenlabs: ElevenLabsIcon,
emailbison: EmailBisonIcon,
enrich: EnrichSoIcon,
evernote: EvernoteIcon,
exa: ExaAIIcon,
@@ -380,6 +382,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
ses: SESIcon,
sftp: SftpIcon,
sharepoint: MicrosoftSharepointIcon,
sharepoint_v2: MicrosoftSharepointIcon,
shopify: ShopifyIcon,
similarweb: SimilarwebIcon,
sixtyfour: SixtyfourIcon,
@@ -0,0 +1,449 @@
---
title: Email Bison
description: Manage Email Bison leads, campaigns, replies, and tags
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="emailbison"
color="#FB7A22"
/>
## 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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
#### 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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
#### 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 |
| --------- | ---- | -------- | ----------- |
| `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 |
| --------- | ---- | -------- | ----------- |
| `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 |
@@ -46,6 +46,7 @@
"dynamodb",
"elasticsearch",
"elevenlabs",
"emailbison",
"enrich",
"evernote",
"exa",
+21 -7
View File
@@ -1,12 +1,12 @@
---
title: Sharepoint
title: SharePoint
description: Work with pages and lists
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="sharepoint"
type="sharepoint_v2"
color="#E0E0E0"
/>
@@ -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
File diff suppressed because it is too large Load Diff
@@ -12,6 +12,7 @@
"calendly",
"circleback",
"confluence",
"emailbison",
"fathom",
"fireflies",
"github",
@@ -75,12 +75,21 @@ const AUTH_STEP: Record<AuthType, string> = {
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?`,
@@ -51,6 +51,7 @@ import {
DynamoDBIcon,
ElasticsearchIcon,
ElevenLabsIcon,
EmailBisonIcon,
EnrichSoIcon,
EvernoteIcon,
ExaAIIcon,
@@ -248,6 +249,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
dynamodb: DynamoDBIcon,
elasticsearch: ElasticsearchIcon,
elevenlabs: ElevenLabsIcon,
emailbison: EmailBisonIcon,
enrich: EnrichSoIcon,
evernote: EvernoteIcon,
exa: ExaAIIcon,
@@ -361,7 +363,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
servicenow: ServiceNowIcon,
ses: SESIcon,
sftp: SftpIcon,
sharepoint: MicrosoftSharepointIcon,
sharepoint_v2: MicrosoftSharepointIcon,
shopify: ShopifyIcon,
similarweb: SimilarwebIcon,
sixtyfour: SixtyfourIcon,
@@ -3637,6 +3637,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",
@@ -11873,9 +11944,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",
@@ -11903,11 +11974,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"
},
{
@@ -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'
@@ -9,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()
@@ -72,42 +75,11 @@ 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[] = []
const skippedFiles: SharepointSkippedFile[] = []
const errors: SharepointUploadError[] = []
for (const userFile of userFiles) {
logger.info(`[${requestId}] Uploading file: ${userFile.name}`)
@@ -119,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
}
@@ -142,7 +120,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}`)
@@ -185,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 {
@@ -215,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
@@ -240,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) {
@@ -263,7 +252,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 }
)
+627
View File
@@ -0,0 +1,627 @@
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
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
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<EmailBisonResponse> = {
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,
triggers: {
enabled: true,
available: [...EMAILBISON_TRIGGER_IDS],
},
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: 'apiBaseUrl',
title: 'Instance URL',
type: 'short-input',
placeholder: 'https://your-emailbison-workspace.com',
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',
},
...EMAILBISON_TRIGGER_IDS.flatMap((triggerId) => getTrigger(triggerId).subBlocks),
],
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) => ({
apiBaseUrl: params.apiBaseUrl,
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' },
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' },
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
}
+483
View File
@@ -14,6 +14,7 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
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,485 @@ 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<SharepointResponse> = {
...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,
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)
const result: Record<string, any> = {
...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' },
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',
},
success: { type: 'boolean', description: 'Success status' },
error: { type: 'string', description: 'Error message' },
},
}
+4 -1
View File
@@ -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'
@@ -180,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'
@@ -283,6 +284,7 @@ export const registry: Record<string, BlockConfig> = {
dub: DubBlock,
duckduckgo: DuckDuckGoBlock,
dynamodb: DynamoDBBlock,
emailbison: EmailBisonBlock,
elasticsearch: ElasticsearchBlock,
elevenlabs: ElevenLabsBlock,
fathom: FathomBlock,
@@ -431,6 +433,7 @@ export const registry: Record<string, BlockConfig> = {
servicenow: ServiceNowBlock,
sftp: SftpBlock,
sharepoint: SharepointBlock,
sharepoint_v2: SharepointV2Block,
shopify: ShopifyBlock,
similarweb: SimilarwebBlock,
sixtyfour: SixtyfourBlock,
+11
View File
@@ -415,6 +415,17 @@ export function MailIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function EmailBisonIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='-66 -2 88 64' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M0 0 C1 2.3 2 4.6 3 7 C7 8.8 7 8.8 11.7 9.9 C13.2 10.3 14.8 10.7 16.4 11.2 C17.6 11.4 18.8 11.7 20 12 C21.5 38 21.5 38 15.8 45.1 C11 49 11 49 7 50 C5.3 49.3 3.7 48.7 2 48 C2 46 2 44 2 42 C0.7 42.3 -0.6 42.7 -2 43 C-1 49.1 0.8 54.3 3 60 C-1 61 -1 61 -7 58 C-7 56.3 -7 54.7 -7 53 C-7.7 54 -8.3 55 -9 56 C-9.7 56.8 -9.7 56.8 -13 61 C-18 60 -18 60 -21 57 C-21.2 56.1 -21.2 56.1 -22 51.3 C-26.1 42.7 -31.1 41.6 -39.7 38.3 C-44 37 -44 37 -47 37 C-53.6 45.2 -56 50.5 -55 61 C-57 60.7 -59 60.3 -61 60 C-65.9 51.1 -63 42.4 -61 33 C-62 34 -63 35 -64 36 C-64.7 36 -65.3 36 -66 36 C-64.4 29.7 -62.6 24 -60 18 C-56.3 16 -52.5 14 -48.6 12.2 C-44 10 -44 10 -39.6 7.3 C-26.4 -0.8 -15.2 -1.1 0 0 Z M-61 27 C-61.3 28 -61.7 29 -62 30 C-61.3 30 -60.7 30 -60 30 C-60.3 29 -60.7 28 -61 27 Z M-7 50 C-6 52 -6 52 -6 52 Z'
fill='currentColor'
/>
</svg>
)
}
export function MailServerIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -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<boolean> {
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<FormatInputResult> {
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<string, unknown> = {
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<SubscriptionResult | undefined> {
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<void> {
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<Record<string, unknown> | null> {
try {
const body: unknown = await response.json()
return isRecord(body) ? body : null
} catch {
return null
}
}
function extractEmailBisonError(body: Record<string, unknown> | 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<string, unknown>): Record<string, unknown> {
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<string, unknown> | null {
return isRecord(value) ? value : null
}
function renameTypeField(value: unknown, targetKey: string): Record<string, unknown> | null {
if (!isRecord(value)) return null
const { type, ...rest } = value
return { ...rest, [targetKey]: type ?? null }
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
@@ -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<string, WebhookProviderHandler> = {
calcom: calcomHandler,
circleback: circlebackHandler,
confluence: confluenceHandler,
emailbison: emailBisonHandler,
fireflies: firefliesHandler,
generic: genericHandler,
gmail: gmailHandler,
@@ -0,0 +1,70 @@
import type {
EmailBisonActionResponse,
EmailBisonAttachLeadsParams,
} from '@/tools/emailbison/types'
import {
actionOutput,
actionOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const attachLeadsToCampaignTool: ToolConfig<
EmailBisonAttachLeadsParams,
EmailBisonActionResponse
> = {
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: {
...emailBisonBaseParamFields,
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`,
{},
params.apiBaseUrl
),
method: 'POST',
headers: emailBisonHeaders,
body: (params) =>
jsonBody({
lead_ids: params.leadIds,
allow_parallel_sending: params.allowParallelSending,
}),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'campaign lead attachment result')
return {
success: true,
output: actionOutput(data),
}
},
outputs: actionOutputs,
}
@@ -0,0 +1,67 @@
import type {
EmailBisonActionResponse,
EmailBisonAttachTagsToLeadsParams,
} from '@/tools/emailbison/types'
import {
actionOutput,
actionOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
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: {
...emailBisonBaseParamFields,
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: (params) => emailBisonUrl('/api/tags/attach-to-leads', {}, params.apiBaseUrl),
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 emailBisonRecordData(response, 'tag attachment result')
return {
success: true,
output: actionOutput(data),
}
},
outputs: actionOutputs,
}
@@ -0,0 +1,58 @@
import type {
EmailBisonCampaignResponse,
EmailBisonCreateCampaignParams,
} from '@/tools/emailbison/types'
import {
campaignOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
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: {
...emailBisonBaseParamFields,
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: (params) => emailBisonUrl('/api/campaigns', {}, params.apiBaseUrl),
method: 'POST',
headers: emailBisonHeaders,
body: (params) =>
jsonBody({
name: params.name,
type: params.campaignType,
}),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'campaign')
return {
success: true,
output: mapCampaign(data),
}
},
outputs: campaignOutputs,
}
+94
View File
@@ -0,0 +1,94 @@
import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types'
import {
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
leadOutputs,
mapLead,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const createLeadTool: ToolConfig<EmailBisonLeadMutationParams, EmailBisonLeadResponse> = {
id: 'emailbison_create_lead',
name: 'Email Bison Create Lead',
description: 'Creates a single lead in Email Bison.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
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', {}, params.apiBaseUrl),
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 emailBisonRecordData(response, 'lead')
return {
success: true,
output: mapLead(data),
}
},
outputs: leadOutputs,
}
+42
View File
@@ -0,0 +1,42 @@
import type { EmailBisonCreateTagParams, EmailBisonTagResponse } from '@/tools/emailbison/types'
import {
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
mapTag,
tagOutputs,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const createTagTool: ToolConfig<EmailBisonCreateTagParams, EmailBisonTagResponse> = {
id: 'emailbison_create_tag',
name: 'Email Bison Create Tag',
description: 'Creates a new Email Bison tag.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Tag name',
},
},
request: {
url: (params) => emailBisonUrl('/api/tags', {}, params.apiBaseUrl),
method: 'POST',
headers: emailBisonHeaders,
body: (params) => jsonBody({ name: params.name }),
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'tag')
return {
success: true,
output: mapTag(data),
}
},
outputs: tagOutputs,
}
+45
View File
@@ -0,0 +1,45 @@
import type { EmailBisonGetLeadParams, EmailBisonLeadResponse } from '@/tools/emailbison/types'
import {
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
leadOutputs,
mapLead,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const getLeadTool: ToolConfig<EmailBisonGetLeadParams, EmailBisonLeadResponse> = {
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: {
...emailBisonBaseParamFields,
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())}`,
{},
params.apiBaseUrl
),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'lead')
return {
success: true,
output: mapLead(data),
}
},
outputs: leadOutputs,
}
+14
View File
@@ -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'
@@ -0,0 +1,42 @@
import type {
EmailBisonBaseParams,
EmailBisonListCampaignsResponse,
} from '@/tools/emailbison/types'
import {
emailBisonArrayData,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonUrl,
listCampaignsOutputs,
mapCampaign,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const listCampaignsTool: ToolConfig<EmailBisonBaseParams, EmailBisonListCampaignsResponse> =
{
id: 'emailbison_list_campaigns',
name: 'Email Bison List Campaigns',
description: 'Retrieves Email Bison campaigns.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
},
request: {
url: (params) => emailBisonUrl('/api/campaigns', {}, params.apiBaseUrl),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonArrayData(response, 'campaigns')
const campaigns = data.map(mapCampaign)
return {
success: true,
output: {
campaigns,
count: campaigns.length,
},
}
},
outputs: listCampaignsOutputs,
}
+85
View File
@@ -0,0 +1,85 @@
import type {
EmailBisonListLeadsParams,
EmailBisonListLeadsResponse,
} from '@/tools/emailbison/types'
import {
emailBisonArrayData,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonUrl,
listLeadsOutputs,
mapLead,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const listLeadsTool: ToolConfig<EmailBisonListLeadsParams, EmailBisonListLeadsResponse> = {
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: {
...emailBisonBaseParamFields,
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,
},
params.apiBaseUrl
),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonArrayData(response, 'leads')
const leads = data.map(mapLead)
return {
success: true,
output: {
leads,
count: leads.length,
},
}
},
outputs: listLeadsOutputs,
}
+108
View File
@@ -0,0 +1,108 @@
import type {
EmailBisonListRepliesParams,
EmailBisonListRepliesResponse,
} from '@/tools/emailbison/types'
import {
emailBisonArrayData,
emailBisonBaseParamFields,
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: {
...emailBisonBaseParamFields,
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,
},
params.apiBaseUrl
),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonArrayData(response, 'replies')
const replies = data.map(mapReply)
return {
success: true,
output: {
replies,
count: replies.length,
},
}
},
outputs: listRepliesOutputs,
}
+38
View File
@@ -0,0 +1,38 @@
import type { EmailBisonBaseParams, EmailBisonListTagsResponse } from '@/tools/emailbison/types'
import {
emailBisonArrayData,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonUrl,
listTagsOutputs,
mapTag,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const listTagsTool: ToolConfig<EmailBisonBaseParams, EmailBisonListTagsResponse> = {
id: 'emailbison_list_tags',
name: 'Email Bison List Tags',
description: 'Retrieves all Email Bison tags for the authenticated workspace.',
version: '1.0.0',
params: {
...emailBisonBaseParamFields,
},
request: {
url: (params) => emailBisonUrl('/api/tags', {}, params.apiBaseUrl),
method: 'GET',
headers: emailBisonHeaders,
},
transformResponse: async (response) => {
const data = await emailBisonArrayData(response, 'tags')
const tags = data.map(mapTag)
return {
success: true,
output: {
tags,
count: tags.length,
},
}
},
outputs: listTagsOutputs,
}
+254
View File
@@ -0,0 +1,254 @@
import type { ToolResponse } from '@/tools/types'
export interface EmailBisonBaseParams {
apiKey: string
apiBaseUrl: string
}
export interface EmailBisonCustomVariable {
name: string | null
value: string | null
}
export interface EmailBisonLeadStats {
emails_sent: number | null
opens: number | null
replies: number | null
unique_replies: number | null
unique_opens: number | null
}
export interface EmailBisonLead {
id: number | null
first_name: string | null
last_name: string | null
email: string | null
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 | null
name: string | null
default: boolean | null
created_at?: string | null
updated_at?: string | null
}
export interface EmailBisonCampaignTag {
id: number | null
name: string | null
default: boolean | null
}
export interface EmailBisonCampaign {
id: number | null
uuid: string | null
name: string | null
type: string | null
status: string | 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
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 | null
}
export interface EmailBisonReplyAttachment {
id: number | null
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 | null
uuid: string | null
folder: string | null
subject: string | null
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 | null
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
@@ -0,0 +1,114 @@
import type {
EmailBisonCampaignResponse,
EmailBisonUpdateCampaignParams,
} from '@/tools/emailbison/types'
import {
campaignOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
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: {
...emailBisonBaseParamFields,
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`, {}, params.apiBaseUrl),
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 emailBisonRecordData(response, 'campaign')
return {
success: true,
output: mapCampaign(data),
}
},
outputs: campaignOutputs,
}
@@ -0,0 +1,64 @@
import type {
EmailBisonCampaignResponse,
EmailBisonCampaignStatusParams,
} from '@/tools/emailbison/types'
import {
campaignOutputs,
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
mapCampaign,
} 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
> = {
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: {
...emailBisonBaseParamFields,
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) => {
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,
},
transformResponse: async (response) => {
const data = await emailBisonRecordData(response, 'campaign')
return {
success: true,
output: mapCampaign(data),
}
},
outputs: campaignOutputs,
}
+106
View File
@@ -0,0 +1,106 @@
import type { EmailBisonLeadMutationParams, EmailBisonLeadResponse } from '@/tools/emailbison/types'
import {
emailBisonBaseParamFields,
emailBisonHeaders,
emailBisonRecordData,
emailBisonUrl,
jsonBody,
leadOutputs,
mapLead,
} from '@/tools/emailbison/utils'
import type { ToolConfig } from '@/tools/types'
export const updateLeadTool: ToolConfig<EmailBisonLeadMutationParams, EmailBisonLeadResponse> = {
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: {
...emailBisonBaseParamFields,
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() ?? '')}`,
{},
params.apiBaseUrl
),
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 emailBisonRecordData(response, 'lead')
return {
success: true,
output: mapLead(data),
}
},
outputs: leadOutputs,
}
+482
View File
@@ -0,0 +1,482 @@
import type {
EmailBisonBaseParams,
EmailBisonCampaign,
EmailBisonCampaignTag,
EmailBisonLead,
EmailBisonLeadStats,
EmailBisonReply,
EmailBisonReplyAddress,
EmailBisonReplyAttachment,
EmailBisonTag,
} from '@/tools/emailbison/types'
import type { OutputProperty, ToolConfig } from '@/tools/types'
type QueryValue = string | number | boolean | Array<string | number> | undefined | null
interface EmailBisonEnvelope<T> {
data?: T
}
export function emailBisonHeaders(params: EmailBisonBaseParams): Record<string, string> {
return {
Authorization: `Bearer ${params.apiKey.trim()}`,
'Content-Type': 'application/json',
}
}
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<string, QueryValue>,
baseUrl: string
): string {
const url = new URL(path, normalizeEmailBisonBaseUrl(baseUrl))
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()
}
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<string, unknown>): Record<string, unknown> {
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined))
}
export async function emailBisonData<T>(response: Response): Promise<T | null> {
const payload = (await response.json()) as EmailBisonEnvelope<T>
return payload.data ?? null
}
export async function emailBisonArrayData(response: Response, label: string): Promise<unknown[]> {
const data = await emailBisonData<unknown[]>(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<unknown> {
const data = await emailBisonData<unknown>(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: 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),
status: toStringOrNull(record.status),
custom_variables: toArray(record.custom_variables).map((item) => {
const variable = toRecord(item)
return {
name: toStringOrNull(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: toNullableNumber(record.id),
uuid: toStringOrNull(record.uuid),
name: toStringOrNull(record.name),
type: toStringOrNull(record.type),
status: toStringOrNull(record.status),
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),
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: toNullableNumber(record.id),
name: toStringOrNull(record.name),
default: toNullableBoolean(record.default),
}
}
export function mapTag(value: unknown): EmailBisonTag {
const record = toRecord(value)
return {
id: toNullableNumber(record.id),
name: toStringOrNull(record.name),
default: toNullableBoolean(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: toNullableNumber(record.id),
uuid: toStringOrNull(record.uuid),
folder: toStringOrNull(record.folder),
subject: toStringOrNull(record.subject),
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: toNullableBoolean(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: record.success === true,
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<ToolConfig['outputs']>
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<string, OutputProperty>
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<string, OutputProperty>
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<ToolConfig['outputs']>
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' },
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<ToolConfig['outputs']>
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<ToolConfig['outputs']>
export const actionOutputs = {
success: { type: 'boolean', description: 'Whether the action succeeded' },
message: { type: 'string', description: 'Action message', optional: true },
} satisfies NonNullable<ToolConfig['outputs']>
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<ToolConfig['outputs']>
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<ToolConfig['outputs']>
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<ToolConfig['outputs']>
function mapLeadStats(record: Record<string, unknown>): EmailBisonLeadStats {
return {
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),
}
}
function mapReplyAddress(value: unknown): EmailBisonReplyAddress {
const record = toRecord(value)
return {
name: toStringOrNull(record.name),
address: toStringOrNull(record.address),
}
}
function mapReplyAttachment(value: unknown): EmailBisonReplyAttachment {
const record = toRecord(value)
return {
id: toNullableNumber(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 isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
function toRecord(value: unknown): Record<string, unknown> {
return isRecord(value) ? value : {}
}
function toArray(value: unknown): unknown[] {
return Array.isArray(value) ? 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 | null {
if (typeof value === 'number') return value
if (typeof value === 'string') {
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : null
}
return null
}
function toNullableNumber(value: unknown): number | null {
if (value === undefined || value === null) return null
return toNumber(value)
}
function toNullableBoolean(value: unknown): boolean | null {
if (value === undefined || value === null) return null
return typeof value === 'boolean' ? value : null
}
+34 -6
View File
@@ -567,6 +567,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,
@@ -4016,6 +4031,19 @@ export const tools: Record<string, ToolConfig> = {
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,
@@ -5249,13 +5277,13 @@ export const tools: Record<string, ToolConfig> = {
hubspot_update_deal: hubspotUpdateDealTool,
hubspot_update_line_item: hubspotUpdateLineItemTool,
hubspot_update_ticket: hubspotUpdateTicketTool,
sharepoint_create_page: sharepointCreatePageTool,
sharepoint_read_page: sharepointReadPageTool,
sharepoint_list_sites: sharepointListSitesTool,
sharepoint_get_list: sharepointGetListTool,
sharepoint_create_list: sharepointCreateListTool,
sharepoint_update_list: sharepointUpdateListItemTool,
sharepoint_add_list_items: sharepointAddListItemTool,
sharepoint_create_list: sharepointCreateListTool,
sharepoint_create_page: sharepointCreatePageTool,
sharepoint_get_list: sharepointGetListTool,
sharepoint_list_sites: sharepointListSitesTool,
sharepoint_read_page: sharepointReadPageTool,
sharepoint_update_list: sharepointUpdateListItemTool,
sharepoint_upload_file: sharepointUploadFileTool,
stripe_create_payment_intent: stripeCreatePaymentIntentTool,
stripe_retrieve_payment_intent: stripeRetrievePaymentIntentTool,
+11 -6
View File
@@ -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<SharepointToolParams, SharepointAddList
id: 'sharepoint_add_list_items',
name: 'Add SharePoint List Item',
description: 'Add a new item to a SharePoint list',
version: '1.0',
version: '1.0.0',
oauth: {
required: true,
@@ -42,7 +43,7 @@ export const addListItemTool: ToolConfig<SharepointToolParams, SharepointAddList
'The ID of the list to add the item to. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012',
},
listItemFields: {
type: 'object',
type: 'json',
required: true,
visibility: 'user-only',
description: 'Field values for the new list item',
@@ -51,11 +52,12 @@ export const addListItemTool: ToolConfig<SharepointToolParams, SharepointAddList
request: {
url: (params) => {
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<SharepointToolParams, SharepointAddList
params.listItemFields !== null &&
'fields' in (params.listItemFields as Record<string, unknown>) &&
Object.keys(params.listItemFields as Record<string, unknown>).length === 1
? ((params.listItemFields as any).fields as Record<string, unknown>)
? ((params.listItemFields as { fields: Record<string, unknown> }).fields as Record<
string,
unknown
>)
: (params.listItemFields as Record<string, unknown>)
if (!providedFields || Object.keys(providedFields).length === 0) {
+31 -15
View File
@@ -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<SharepointToolParams, SharepointCreateLi
id: 'sharepoint_create_list',
name: 'Create SharePoint List',
description: 'Create a new list in a SharePoint site',
version: '1.0',
version: '1.0.0',
oauth: {
required: true,
@@ -59,7 +60,7 @@ export const createListTool: ToolConfig<SharepointToolParams, SharepointCreateLi
description: "List template name (e.g., 'genericList')",
},
pageContent: {
type: 'string',
type: 'json',
required: false,
visibility: 'user-only',
description:
@@ -69,7 +70,7 @@ export const createListTool: ToolConfig<SharepointToolParams, SharepointCreateLi
request: {
url: (params) => {
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<SharepointToolParams, SharepointCreateLi
Accept: 'application/json',
}),
body: (params) => {
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', {
+10 -10
View File
@@ -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<SharepointToolParams, SharepointCreatePa
id: 'sharepoint_create_page',
name: 'Create SharePoint Page',
description: 'Create a new page in a SharePoint site',
version: '1.0',
version: '1.0.0',
oauth: {
required: true,
@@ -60,8 +61,7 @@ export const createPageTool: ToolConfig<SharepointToolParams, SharepointCreatePa
request: {
url: (params) => {
// 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<SharepointToolParams, SharepointCreatePa
Accept: 'application/json',
}),
body: (params) => {
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<SharepointToolParams, SharepointCreatePa
pageLayout: 'article',
}
// Add content if provided using the simple innerHtml approach from the documentation
if (params.pageContent) {
const pageContent = typeof params.pageContent === 'string' ? params.pageContent : undefined
if (pageContent) {
pageData.canvasLayout = {
horizontalSections: [
{
@@ -103,7 +103,7 @@ export const createPageTool: ToolConfig<SharepointToolParams, SharepointCreatePa
webparts: [
{
id: '6f9230af-2a98-4952-b205-9ede4f9ef548',
innerHtml: `<p>${params.pageContent.replace(/"/g, '&quot;').replace(/'/g, '&#39;')}</p>`,
innerHtml: `<p>${pageContent.replace(/"/g, '&quot;').replace(/'/g, '&#39;')}</p>`,
},
],
},
+55 -37
View File
@@ -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<SharepointToolParams, SharepointGetListResp
id: 'sharepoint_get_list',
name: 'Get SharePoint List',
description: 'Get metadata (and optionally columns/items) for a SharePoint list',
version: '1.0',
version: '1.0.0',
oauth: {
required: true,
@@ -45,14 +46,36 @@ export const getListTool: ToolConfig<SharepointToolParams, SharepointGetListResp
description:
'The ID of the list to retrieve. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012',
},
includeColumns: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Whether to include column definitions when retrieving a specific list',
},
includeItems: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Whether to include list items when retrieving a specific list',
},
nextPageUrl: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Full @odata.nextLink URL from a previous Microsoft Graph page response',
},
},
request: {
url: (params) => {
const siteId = params.siteId || params.siteSelector || 'root'
if (params.nextPageUrl) {
return assertGraphNextPageUrl(params.nextPageUrl)
}
// If neither listId nor listTitle provided, list all lists in the site
if (!params.listId) {
const siteId = optionalTrim(params.siteId) || optionalTrim(params.siteSelector) || 'root'
const listId = optionalTrim(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<SharepointToolParams, SharepointGetListResp
return finalUrl
}
const listSegment = params.listId
// Default to returning items when targeting a specific list unless explicitly disabled
const listSegment = encodeURIComponent(listId)
const wantsItems = typeof params.includeItems === 'boolean' ? params.includeItems : true
// If caller wants items for a specific list, prefer the items endpoint (no columns)
if (wantsItems && !params.includeColumns) {
const itemsUrl = new URL(
`https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listSegment}/items`
@@ -77,12 +98,11 @@ export const getListTool: ToolConfig<SharepointToolParams, SharepointGetListResp
logger.info('SharePoint Get List Items URL', {
finalUrl: finalItemsUrl,
siteId,
listId: params.listId,
listId,
})
return finalItemsUrl
}
// Otherwise, fetch list metadata (optionally with columns/items via $expand)
const baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listSegment}`
const url = new URL(baseUrl)
const expandParts: string[] = []
@@ -94,7 +114,7 @@ export const getListTool: ToolConfig<SharepointToolParams, SharepointGetListResp
logger.info('SharePoint Get List URL', {
finalUrl,
siteId,
listId: params.listId,
listId,
includeColumns: !!params.includeColumns,
includeItems: wantsItems,
})
@@ -107,7 +127,7 @@ export const getListTool: ToolConfig<SharepointToolParams, SharepointGetListResp
}),
},
transformResponse: async (response: Response) => {
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<SharepointToolParams, SharepointGetListResp
fields: i.fields as Record<string, unknown>,
}))
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<string, unknown>)
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<SharepointToolParams, SharepointGetListResp
list: l.list,
}))
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<string, unknown>)
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<SharepointToolParams, SharepointGetListResp
description: 'All lists in the site when no listId/title provided',
items: { type: 'object' },
},
items: {
type: 'array',
description: 'List items with expanded fields when reading list items',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Item ID' },
fields: { type: 'object', description: 'Field values for the item' },
},
},
},
nextPageUrl: {
type: 'string',
description: 'Full Microsoft Graph @odata.nextLink URL for the next page of results',
optional: true,
},
},
}
+2
View File
@@ -15,3 +15,5 @@ export const sharepointReadPageTool = readPageTool
export const sharepointUpdateListItemTool = updateListItemTool
export const sharepointAddListItemTool = addListItemTool
export const sharepointUploadFileTool = uploadFileTool
export * from '@/tools/sharepoint/types'
+32 -14
View File
@@ -3,13 +3,14 @@ import type {
SharepointSite,
SharepointToolParams,
} from '@/tools/sharepoint/types'
import { assertGraphNextPageUrl, getGraphNextPageUrl, optionalTrim } from '@/tools/sharepoint/utils'
import type { ToolConfig } from '@/tools/types'
export const listSitesTool: ToolConfig<SharepointToolParams, SharepointReadSiteResponse> = {
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<SharepointToolParams, SharepointReadSiteR
visibility: 'user-only',
description: 'Select the SharePoint site',
},
siteId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The ID of the SharePoint site (internal use)',
},
groupId: {
type: 'string',
required: false,
@@ -36,27 +43,34 @@ export const listSitesTool: ToolConfig<SharepointToolParams, SharepointReadSiteR
description:
'The group ID for accessing a group team site. Example: a GUID like 12345678-1234-1234-1234-123456789012',
},
nextPageUrl: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Full @odata.nextLink URL from a previous Microsoft Graph page response',
},
},
request: {
url: (params) => {
let baseUrl: string
if (params.nextPageUrl) {
return assertGraphNextPageUrl(params.nextPageUrl)
}
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
let baseUrl: string
const groupId = optionalTrim(params.groupId)
const siteId = optionalTrim(params.siteId) || optionalTrim(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<SharepointToolParams, SharepointReadSiteR
}),
},
transformResponse: async (response: Response, params) => {
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<SharepointToolParams, SharepointReadSiteR
createdDateTime: site.createdDateTime,
lastModifiedDateTime: site.lastModifiedDateTime,
})),
nextPageUrl: getGraphNextPageUrl(data as Record<string, unknown>),
},
}
}
// Single site response
return {
success: true,
output: {
@@ -155,5 +168,10 @@ export const listSitesTool: ToolConfig<SharepointToolParams, SharepointReadSiteR
},
},
},
nextPageUrl: {
type: 'string',
description: 'Full Microsoft Graph @odata.nextLink URL for the next page of results',
optional: true,
},
},
}
+59 -31
View File
@@ -6,7 +6,14 @@ import type {
SharepointReadPageResponse,
SharepointToolParams,
} from '@/tools/sharepoint/types'
import { cleanODataMetadata, extractTextFromCanvasLayout } from '@/tools/sharepoint/utils'
import {
assertGraphNextPageUrl,
cleanODataMetadata,
escapeODataString,
extractTextFromCanvasLayout,
getGraphNextPageUrl,
optionalTrim,
} from '@/tools/sharepoint/utils'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('SharePointReadPage')
@@ -15,7 +22,7 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
id: 'sharepoint_read_page',
name: 'Read SharePoint Page',
description: 'Read a specific page from a SharePoint site',
version: '1.0',
version: '1.0.0',
oauth: {
required: true,
@@ -62,48 +69,56 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
description:
'Maximum number of pages to return when listing all pages (default: 10, max: 50)',
},
nextPageUrl: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Full @odata.nextLink URL from a previous Microsoft Graph page response',
},
},
request: {
url: (params) => {
// 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`
const escapedPageName = escapeODataString(pageName)
const escapedPageNameWithAspx = escapeODataString(pageNameWithAspx)
// 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
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<SharepointToolParams, SharepointReadPageRe
logger.info('SharePoint API URL', {
finalUrl,
siteId,
pageId: params.pageId,
pageId,
pageName: params.pageName,
searchParams: Object.fromEntries(url.searchParams),
})
@@ -138,7 +153,6 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
})
if (params?.pageId) {
// Direct page access - return single page
const pageData = data
const contentData = {
content: extractTextFromCanvasLayout(data.canvasLayout),
@@ -154,6 +168,7 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
title: pageData.title || pageData.name!,
webUrl: pageData.webUrl!,
pageLayout: pageData.pageLayout,
description: pageData.description ?? null,
createdDateTime: pageData.createdDateTime,
lastModifiedDateTime: pageData.lastModifiedDateTime,
},
@@ -161,7 +176,7 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
},
}
}
// Multiple pages or search by name
if (!data.value || data.value.length === 0) {
logger.info('No pages found', {
searchName: params?.pageName,
@@ -189,7 +204,6 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
})
if (params?.pageName) {
// Search by name - return single page (first match)
const pageData = data.value[0]
const siteId = params?.siteId || params?.siteSelector || 'root'
const contentUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/pages/${pageData.id}/microsoft.graph.sitePage?$expand=canvasLayout`
@@ -230,6 +244,7 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
title: pageData.title || pageData.name,
webUrl: pageData.webUrl,
pageLayout: pageData.pageLayout,
description: pageData.description ?? null,
createdDateTime: pageData.createdDateTime,
lastModifiedDateTime: pageData.lastModifiedDateTime,
},
@@ -237,16 +252,16 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
},
}
}
// List all pages - return multiple pages with content
const siteId = params?.siteId || params?.siteSelector || 'root'
const pagesWithContent = []
const nextPageUrl = getGraphNextPageUrl(data)
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<SharepointToolParams, SharepointReadPageRe
title: pageInfo.title || pageInfo.name,
webUrl: pageInfo.webUrl,
pageLayout: pageInfo.pageLayout,
description: pageInfo.description ?? null,
createdDateTime: pageInfo.createdDateTime,
lastModifiedDateTime: pageInfo.lastModifiedDateTime,
},
@@ -292,7 +308,6 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
error: toError(error).message,
})
// Still add the page without content
pagesWithContent.push({
page: {
id: pageInfo.id,
@@ -300,6 +315,7 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
title: pageInfo.title || pageInfo.name,
webUrl: pageInfo.webUrl,
pageLayout: pageInfo.pageLayout,
description: pageInfo.description ?? null,
createdDateTime: pageInfo.createdDateTime,
lastModifiedDateTime: pageInfo.lastModifiedDateTime,
},
@@ -320,6 +336,7 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
output: {
pages: pagesWithContent,
totalPages: pagesWithContent.length,
nextPageUrl,
},
}
},
@@ -334,6 +351,7 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
title: { type: 'string', description: 'The title of the page' },
webUrl: { type: 'string', description: 'The URL to access the page' },
pageLayout: { type: 'string', description: 'The layout type of the page' },
description: { type: 'string', description: 'The description of the page', optional: true },
createdDateTime: { type: 'string', description: 'When the page was created' },
lastModifiedDateTime: { type: 'string', description: 'When the page was last modified' },
},
@@ -352,6 +370,11 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
title: { type: 'string', description: 'The title of the page' },
webUrl: { type: 'string', description: 'The URL to access the page' },
pageLayout: { type: 'string', description: 'The layout type of the page' },
description: {
type: 'string',
description: 'The description of the page',
optional: true,
},
createdDateTime: { type: 'string', description: 'When the page was created' },
lastModifiedDateTime: {
type: 'string',
@@ -381,5 +404,10 @@ export const readPageTool: ToolConfig<SharepointToolParams, SharepointReadPageRe
},
},
totalPages: { type: 'number', description: 'Total number of pages found' },
nextPageUrl: {
type: 'string',
description: 'Full Microsoft Graph @odata.nextLink URL for the next page of results',
optional: true,
},
},
}
+29 -37
View File
@@ -6,7 +6,7 @@ export interface SharepointSite {
name: string
displayName: string
webUrl: string
description?: string
description?: string | null
createdDateTime?: string
lastModifiedDateTime?: string
}
@@ -16,6 +16,7 @@ export interface SharepointPage {
id?: string
name: string
title: string
description?: string | null
webUrl?: string
pageLayout?: string
createdDateTime?: string
@@ -95,7 +96,7 @@ export interface SharepointList {
export interface SharepointListSitesResponse extends ToolResponse {
output: {
sites: SharepointSite[]
nextPageToken?: string
nextPageUrl?: string
}
}
@@ -116,6 +117,7 @@ export interface SharepointReadPageResponse extends ToolResponse {
pages?: SharepointPageWithContent[]
content?: SharepointPageContent
totalPages?: number
nextPageUrl?: string
}
}
@@ -126,7 +128,7 @@ export interface SharepointReadSiteResponse extends ToolResponse {
name: string
displayName: string
webUrl: string
description?: string
description?: string | null
createdDateTime?: string
lastModifiedDateTime?: string
isPersonalSite?: boolean
@@ -142,10 +144,11 @@ export interface SharepointReadSiteResponse extends ToolResponse {
name: string
displayName: string
webUrl: string
description?: string
description?: string | null
createdDateTime?: string
lastModifiedDateTime?: string
}>
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
}
}
@@ -309,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[]
}
}
+11 -8
View File
@@ -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) => ({
+48 -12
View File
@@ -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<SharepointToolParams, SharepointUploadFileResponse> = {
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<SharepointToolParams, SharepointUploadFi
},
files: {
type: 'file[]',
required: false,
required: true,
visibility: 'user-only',
description: 'Files to upload to SharePoint',
},
@@ -62,10 +63,10 @@ export const uploadFileTool: ToolConfig<SharepointToolParams, SharepointUploadFi
body: (params: SharepointToolParams) => {
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,
}
},
@@ -73,15 +74,17 @@ export const uploadFileTool: ToolConfig<SharepointToolParams, SharepointUploadFi
transformResponse: async (response: Response) => {
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',
}
},
@@ -105,5 +108,38 @@ export const uploadFileTool: ToolConfig<SharepointToolParams, SharepointUploadFi
type: 'number',
description: 'Number of files uploaded',
},
skippedFiles: {
type: 'array',
description: 'Files that were skipped before upload',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'File name' },
size: { type: 'number', description: 'File size in bytes' },
limit: { type: 'number', description: 'Upload size limit in bytes' },
reason: { type: 'string', description: 'Reason the file was skipped' },
},
},
},
skippedCount: {
type: 'number',
description: 'Number of files skipped',
},
errors: {
type: 'array',
description: 'Per-file upload errors',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'File name' },
error: { type: 'string', description: 'Error message' },
status: {
type: 'number',
description: 'HTTP status from Microsoft Graph',
optional: true,
},
},
},
},
},
}
+24
View File
@@ -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: object): string | undefined {
const nextLink = (data as Record<string, unknown>)['@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
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
+17
View File
@@ -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'
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
@@ -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' } },
}
+510
View File
@@ -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 <strong>Settings &gt; Developer API</strong>.',
'Enter the <strong>Instance URL</strong> from Email Bison&rsquo;s webhook payload, Full API Reference, or exported Postman collection.',
`Click <strong>Save Configuration</strong> to automatically create an Email Bison webhook for <strong>${eventType}</strong>.`,
'The webhook will be automatically deleted from Email Bison when this trigger is removed.',
]
return instructions
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.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<string, TriggerOutput> {
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<string, TriggerOutput> {
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<string, TriggerOutput> {
return buildEmailBisonEmailSentOutputs()
}
export function buildEmailBisonLeadUnsubscribedOutputs(): Record<string, TriggerOutput> {
return buildEmailBisonEmailSentOutputs()
}
export function buildEmailBisonEmailOpenedOutputs(): Record<string, TriggerOutput> {
return buildEmailBisonEmailSentOutputs()
}
export function buildEmailBisonLeadRepliedOutputs(): Record<string, TriggerOutput> {
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<string, TriggerOutput> {
return buildEmailBisonLeadRepliedOutputs()
}
export function buildEmailBisonEmailBouncedOutputs(): Record<string, TriggerOutput> {
return buildEmailBisonLeadRepliedOutputs()
}
export function buildEmailBisonManualEmailSentOutputs(): Record<string, TriggerOutput> {
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<string, TriggerOutput> {
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<string, TriggerOutput> {
return buildEmailBisonTagAttachedOutputs()
}
export function buildEmailBisonEmailAccountAddedOutputs(): Record<string, TriggerOutput> {
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<string, TriggerOutput> {
return buildEmailBisonEmailAccountAddedOutputs()
}
export function buildEmailBisonEmailAccountDisconnectedOutputs(): Record<string, TriggerOutput> {
return buildEmailBisonEmailAccountAddedOutputs()
}
export function buildEmailBisonEmailAccountReconnectedOutputs(): Record<string, TriggerOutput> {
return buildEmailBisonEmailAccountAddedOutputs()
}
export function buildEmailBisonWarmupDisabledReceivingBouncesOutputs(): Record<
string,
TriggerOutput
> {
return buildEmailBisonEmailAccountAddedOutputs()
}
export function buildEmailBisonWarmupDisabledCausingBouncesOutputs(): Record<
string,
TriggerOutput
> {
return buildEmailBisonEmailAccountAddedOutputs()
}
export function buildEmailBisonUntrackedReplyReceivedOutputs(): Record<string, TriggerOutput> {
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<string, unknown>): 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<string, unknown> {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
@@ -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' } },
}
@@ -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' } },
}
+36
View File
@@ -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,