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