Skip to content

Commit 56dae41

Browse files
committed
improvement(webhooks): move non-polling off trigger.dev
1 parent d5502d6 commit 56dae41

File tree

11 files changed

+64
-50
lines changed

11 files changed

+64
-50
lines changed

apps/sim/app/api/webhooks/route.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,7 @@ export async function POST(request: NextRequest) {
367367
)
368368
}
369369

370-
// Configure each new webhook (for providers that need configuration)
371-
const pollingProviders = ['gmail', 'outlook']
372-
const needsConfiguration = pollingProviders.includes(provider)
370+
const needsConfiguration = provider === 'gmail' || provider === 'outlook'
373371

374372
if (needsConfiguration) {
375373
const configureFunc =

apps/sim/lib/core/async-jobs/config.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const logger = createLogger('AsyncJobsConfig')
77

88
let cachedBackend: JobQueueBackend | null = null
99
let cachedBackendType: AsyncBackendType | null = null
10+
let cachedInlineBackend: JobQueueBackend | null = null
1011

1112
/**
1213
* Determines which async backend to use based on environment configuration.
@@ -71,6 +72,30 @@ export function getCurrentBackendType(): AsyncBackendType | null {
7172
return cachedBackendType
7273
}
7374

75+
/**
76+
* Gets a job queue backend that bypasses Trigger.dev (Redis -> Database).
77+
* Used for non-polling webhooks that should always execute inline.
78+
*/
79+
export async function getInlineJobQueue(): Promise<JobQueueBackend> {
80+
if (cachedInlineBackend) {
81+
return cachedInlineBackend
82+
}
83+
84+
const redis = getRedisClient()
85+
if (redis) {
86+
const { RedisJobQueue } = await import('@/lib/core/async-jobs/backends/redis')
87+
cachedInlineBackend = new RedisJobQueue(redis)
88+
} else {
89+
const { DatabaseJobQueue } = await import('@/lib/core/async-jobs/backends/database')
90+
cachedInlineBackend = new DatabaseJobQueue()
91+
}
92+
93+
logger.info(
94+
`Inline job backend initialized: ${cachedInlineBackend ? 'redis or database' : 'none'}`
95+
)
96+
return cachedInlineBackend
97+
}
98+
7499
/**
75100
* Checks if jobs should be executed inline (fire-and-forget).
76101
* For Redis/DB backends, we execute inline. Trigger.dev handles execution itself.
@@ -85,4 +110,5 @@ export function shouldExecuteInline(): boolean {
85110
export function resetJobQueueCache(): void {
86111
cachedBackend = null
87112
cachedBackendType = null
113+
cachedInlineBackend = null
88114
}

apps/sim/lib/core/async-jobs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export {
22
getAsyncBackendType,
33
getCurrentBackendType,
4+
getInlineJobQueue,
45
getJobQueue,
56
resetJobQueueCache,
67
shouldExecuteInline,

apps/sim/lib/webhooks/processor.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { and, eq, isNull, or } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { v4 as uuidv4 } from 'uuid'
77
import { checkEnterprisePlan, checkTeamPlan } from '@/lib/billing/subscriptions/utils'
8-
import { getJobQueue, shouldExecuteInline } from '@/lib/core/async-jobs'
8+
import { getInlineJobQueue, getJobQueue, shouldExecuteInline } from '@/lib/core/async-jobs'
99
import { isProd } from '@/lib/core/config/feature-flags'
1010
import { safeCompare } from '@/lib/core/security/encryption'
1111
import { getEffectiveDecryptedEnv } from '@/lib/environment/utils'
@@ -29,6 +29,7 @@ import {
2929
import { executeWebhookJob } from '@/background/webhook-execution'
3030
import { resolveEnvVarReferences } from '@/executor/utils/reference-validation'
3131
import { isConfluencePayloadMatch } from '@/triggers/confluence/utils'
32+
import { isPollingWebhookProvider } from '@/triggers/constants'
3233
import { isGitHubEventMatch } from '@/triggers/github/utils'
3334
import { isHubSpotContactEventMatch } from '@/triggers/hubspot/utils'
3435
import { isJiraEventMatch } from '@/triggers/jira/utils'
@@ -1116,15 +1117,24 @@ export async function queueWebhookExecution(
11161117
...(credentialId ? { credentialId } : {}),
11171118
}
11181119

1119-
const jobQueue = await getJobQueue()
1120-
const jobId = await jobQueue.enqueue('webhook-execution', payload, {
1121-
metadata: { workflowId: foundWorkflow.id, userId: actorUserId },
1122-
})
1123-
logger.info(
1124-
`[${options.requestId}] Queued webhook execution task ${jobId} for ${foundWebhook.provider} webhook`
1125-
)
1120+
const isPolling = isPollingWebhookProvider(payload.provider)
11261121

1127-
if (shouldExecuteInline()) {
1122+
if (isPolling && !shouldExecuteInline()) {
1123+
const jobQueue = await getJobQueue()
1124+
const jobId = await jobQueue.enqueue('webhook-execution', payload, {
1125+
metadata: { workflowId: foundWorkflow.id, userId: actorUserId },
1126+
})
1127+
logger.info(
1128+
`[${options.requestId}] Queued polling webhook execution task ${jobId} for ${foundWebhook.provider} webhook via job queue`
1129+
)
1130+
} else {
1131+
const jobQueue = await getInlineJobQueue()
1132+
const jobId = await jobQueue.enqueue('webhook-execution', payload, {
1133+
metadata: { workflowId: foundWorkflow.id, userId: actorUserId },
1134+
})
1135+
logger.info(
1136+
`[${options.requestId}] Executing ${foundWebhook.provider} webhook ${jobId} inline`
1137+
)
11281138
void (async () => {
11291139
try {
11301140
await jobQueue.startJob(jobId)

apps/sim/lib/webhooks/utils.server.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
refreshAccessTokenIfNeeded,
2020
resolveOAuthAccountId,
2121
} from '@/app/api/auth/oauth/utils'
22+
import { isPollingWebhookProvider } from '@/triggers/constants'
2223

2324
const logger = createLogger('WebhookUtils')
2425

@@ -2222,10 +2223,7 @@ export async function syncWebhooksForCredentialSet(params: {
22222223
`[${requestId}] Syncing webhooks for credential set ${credentialSetId}, provider ${provider}`
22232224
)
22242225

2225-
// Polling providers get unique paths per credential (for independent state)
2226-
// External webhook providers share the same path (external service sends to one URL)
2227-
const pollingProviders = ['gmail', 'outlook', 'rss', 'imap']
2228-
const useUniquePaths = pollingProviders.includes(provider)
2226+
const useUniquePaths = isPollingWebhookProvider(provider)
22292227

22302228
const credentials = await getCredentialsForCredentialSet(credentialSetId, oauthProviderId)
22312229

apps/sim/triggers/constants.ts

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,11 @@
11
/**
2-
* System subblock IDs that are part of the trigger UI infrastructure
3-
* and should NOT be aggregated into triggerConfig or validated as user fields.
4-
*
5-
* These subblocks provide UI/UX functionality but aren't configuration data.
2+
* Set of webhook provider names that use polling-based triggers.
3+
* Mirrors the `polling: true` flag on TriggerConfig entries.
4+
* Used to route execution: polling providers use the full job queue
5+
* (Trigger.dev), non-polling providers execute inline.
66
*/
7-
export const SYSTEM_SUBBLOCK_IDS: string[] = [
8-
'triggerCredentials', // OAuth credentials subblock
9-
'triggerInstructions', // Setup instructions text
10-
'webhookUrlDisplay', // Webhook URL display
11-
'samplePayload', // Example payload display
12-
'setupScript', // Setup script code (e.g., Apps Script)
13-
'scheduleInfo', // Schedule status display (next run, last run)
14-
]
7+
export const POLLING_PROVIDERS = new Set(['gmail', 'outlook', 'rss', 'imap'])
158

16-
/**
17-
* Trigger-related subblock IDs that represent runtime metadata. They should remain
18-
* in the workflow state but must not be modified or cleared by diff operations.
19-
*
20-
* Note: 'triggerConfig' is included because it's an aggregate of individual trigger
21-
* field subblocks. Those individual fields are compared separately, so comparing
22-
* triggerConfig would be redundant. Additionally, the client populates triggerConfig
23-
* with default values from the trigger definition on load, which aren't present in
24-
* the deployed state, causing false positive change detection.
25-
*/
26-
export const TRIGGER_RUNTIME_SUBBLOCK_IDS: string[] = [
27-
'webhookId',
28-
'triggerPath',
29-
'triggerConfig',
30-
'triggerId',
31-
]
32-
33-
/**
34-
* Maximum number of consecutive failures before a trigger (schedule/webhook) is auto-disabled.
35-
* This prevents runaway errors from continuously executing failing workflows.
36-
*/
37-
export const MAX_CONSECUTIVE_FAILURES = 100
9+
export function isPollingWebhookProvider(provider: string): boolean {
10+
return POLLING_PROVIDERS.has(provider)
11+
}

apps/sim/triggers/gmail/poller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const gmailPollingTrigger: TriggerConfig = {
3030
description: 'Triggers when new emails are received in Gmail (requires Gmail credentials)',
3131
version: '1.0.0',
3232
icon: GmailIcon,
33+
polling: true,
3334

3435
subBlocks: [
3536
{

apps/sim/triggers/imap/poller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const imapPollingTrigger: TriggerConfig = {
1212
description: 'Triggers when new emails are received via IMAP (works with any email provider)',
1313
version: '1.0.0',
1414
icon: MailServerIcon,
15+
polling: true,
1516

1617
subBlocks: [
1718
// Connection settings

apps/sim/triggers/outlook/poller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const outlookPollingTrigger: TriggerConfig = {
2424
description: 'Triggers when new emails are received in Outlook (requires Microsoft credentials)',
2525
version: '1.0.0',
2626
icon: OutlookIcon,
27+
polling: true,
2728

2829
subBlocks: [
2930
{

apps/sim/triggers/rss/poller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const rssPollingTrigger: TriggerConfig = {
88
description: 'Triggers when new items are published to an RSS feed',
99
version: '1.0.0',
1010
icon: RssIcon,
11+
polling: true,
1112

1213
subBlocks: [
1314
{

0 commit comments

Comments
 (0)