Skip to content

Commit 3b9f0f9

Browse files
feat(error-notifications): workspace-level configuration of slack, email, webhook notifications for workflow execution (#2157)
* feat(notification): slack, email, webhook notifications from logs * retain search params for filters to link in notification * add alerting rules * update selector * fix lint * add limits on num of emails and notification triggers per workspace * address greptile comments * add search to combobox * move notifications to react query * fix lint * fix email formatting * add more alert types * fix imports * fix test route * use emcn componentfor modal * refactor: consolidate notification config fields into jsonb objects * regen migration * fix delete notif modal ui * make them multiselect dropdowns * update tag styling * combobox font size with multiselect tags'
1 parent dcbdcb4 commit 3b9f0f9

File tree

32 files changed

+12461
-2453
lines changed

32 files changed

+12461
-2453
lines changed

apps/docs/content/docs/en/execution/api.mdx

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -240,32 +240,78 @@ Retrieve execution details including the workflow state snapshot.
240240
</Tab>
241241
</Tabs>
242242

243-
## Webhook Subscriptions
243+
## Notifications
244244

245-
Get real-time notifications when workflow executions complete. Webhooks are configured through the Sim UI in the workflow editor.
245+
Get real-time notifications when workflow executions complete via webhook, email, or Slack. Notifications are configured at the workspace level from the Logs page.
246246

247247
### Configuration
248248

249-
Webhooks can be configured for each workflow through the workflow editor UI. Click the webhook icon in the control bar to set up your webhook subscriptions.
249+
Configure notifications from the Logs page by clicking the menu button and selecting "Configure Notifications".
250250

251-
<div className="mx-auto w-full overflow-hidden rounded-lg">
252-
<Video src="configure-webhook.mp4" width={700} height={450} />
253-
</div>
251+
**Notification Channels:**
252+
- **Webhook**: Send HTTP POST requests to your endpoint
253+
- **Email**: Receive email notifications with execution details
254+
- **Slack**: Post messages to a Slack channel
254255

255-
**Available Configuration Options:**
256+
**Workflow Selection:**
257+
- Select specific workflows to monitor
258+
- Or choose "All Workflows" to include current and future workflows
259+
260+
**Filtering Options:**
261+
- `levelFilter`: Log levels to receive (`info`, `error`)
262+
- `triggerFilter`: Trigger types to receive (`api`, `webhook`, `schedule`, `manual`, `chat`)
263+
264+
**Optional Data:**
265+
- `includeFinalOutput`: Include the workflow's final output
266+
- `includeTraceSpans`: Include detailed execution trace spans
267+
- `includeRateLimits`: Include rate limit information (sync/async limits and remaining)
268+
- `includeUsageData`: Include billing period usage and limits
269+
270+
### Alert Rules
271+
272+
Instead of receiving notifications for every execution, configure alert rules to be notified only when issues are detected:
273+
274+
**Consecutive Failures**
275+
- Alert after X consecutive failed executions (e.g., 3 failures in a row)
276+
- Resets when an execution succeeds
277+
278+
**Failure Rate**
279+
- Alert when failure rate exceeds X% over the last Y hours
280+
- Requires minimum 5 executions in the window
281+
- Only triggers after the full time window has elapsed
282+
283+
**Latency Threshold**
284+
- Alert when any execution takes longer than X seconds
285+
- Useful for catching slow or hanging workflows
286+
287+
**Latency Spike**
288+
- Alert when execution is X% slower than the average
289+
- Compares against the average duration over the configured time window
290+
- Requires minimum 5 executions to establish baseline
291+
292+
**Cost Threshold**
293+
- Alert when a single execution costs more than $X
294+
- Useful for catching expensive LLM calls
295+
296+
**No Activity**
297+
- Alert when no executions occur within X hours
298+
- Useful for monitoring scheduled workflows that should run regularly
299+
300+
**Error Count**
301+
- Alert when error count exceeds X within a time window
302+
- Tracks total errors, not consecutive
303+
304+
All alert types include a 1-hour cooldown to prevent notification spam.
305+
306+
### Webhook Configuration
307+
308+
For webhooks, additional options are available:
256309
- `url`: Your webhook endpoint URL
257310
- `secret`: Optional secret for HMAC signature verification
258-
- `includeFinalOutput`: Include the workflow's final output in the payload
259-
- `includeTraceSpans`: Include detailed execution trace spans
260-
- `includeRateLimits`: Include the workflow owner's rate limit information
261-
- `includeUsageData`: Include the workflow owner's usage and billing data
262-
- `levelFilter`: Array of log levels to receive (`info`, `error`)
263-
- `triggerFilter`: Array of trigger types to receive (`api`, `webhook`, `schedule`, `manual`, `chat`)
264-
- `active`: Enable/disable the webhook subscription
265311

266-
### Webhook Payload
312+
### Payload Structure
267313

268-
When a workflow execution completes, Sim sends a POST request to your webhook URL:
314+
When a workflow execution completes, Sim sends the following payload (via webhook POST, email, or Slack):
269315

270316
```json
271317
{
@@ -316,7 +362,7 @@ When a workflow execution completes, Sim sends a POST request to your webhook UR
316362

317363
### Webhook Headers
318364

319-
Each webhook request includes these headers:
365+
Each webhook request includes these headers (webhook channel only):
320366

321367
- `sim-event`: Event type (always `workflow.execution.completed`)
322368
- `sim-timestamp`: Unix timestamp in milliseconds

apps/docs/content/docs/en/execution/logging.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,4 @@ The snapshot provides:
147147

148148
- Learn about [Cost Calculation](/execution/costs) to understand workflow pricing
149149
- Explore the [External API](/execution/api) for programmatic log access
150-
- Set up [Webhook notifications](/execution/api#webhook-subscriptions) for real-time alerts
150+
- Set up [Notifications](/execution/api#notifications) for real-time alerts via webhook, email, or Slack
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { db } from '@sim/db'
2+
import { account } from '@sim/db/schema'
3+
import { and, eq } from 'drizzle-orm'
4+
import { type NextRequest, NextResponse } from 'next/server'
5+
import { getSession } from '@/lib/auth'
6+
import { createLogger } from '@/lib/logs/console/logger'
7+
8+
const logger = createLogger('AuthAccountsAPI')
9+
10+
export async function GET(request: NextRequest) {
11+
try {
12+
const session = await getSession()
13+
if (!session?.user?.id) {
14+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
15+
}
16+
17+
const { searchParams } = new URL(request.url)
18+
const provider = searchParams.get('provider')
19+
20+
const whereConditions = [eq(account.userId, session.user.id)]
21+
22+
if (provider) {
23+
whereConditions.push(eq(account.providerId, provider))
24+
}
25+
26+
const accounts = await db
27+
.select({
28+
id: account.id,
29+
accountId: account.accountId,
30+
providerId: account.providerId,
31+
})
32+
.from(account)
33+
.where(and(...whereConditions))
34+
35+
return NextResponse.json({ accounts })
36+
} catch (error) {
37+
logger.error('Failed to fetch accounts', { error })
38+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
39+
}
40+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { nanoid } from 'nanoid'
2+
import { type NextRequest, NextResponse } from 'next/server'
3+
import { verifyCronAuth } from '@/lib/auth/internal'
4+
import { acquireLock, releaseLock } from '@/lib/core/config/redis'
5+
import { createLogger } from '@/lib/logs/console/logger'
6+
import { pollInactivityAlerts } from '@/lib/notifications/inactivity-polling'
7+
8+
const logger = createLogger('InactivityAlertPoll')
9+
10+
export const maxDuration = 120
11+
12+
const LOCK_KEY = 'inactivity-alert-polling-lock'
13+
const LOCK_TTL_SECONDS = 120
14+
15+
export async function GET(request: NextRequest) {
16+
const requestId = nanoid()
17+
logger.info(`Inactivity alert polling triggered (${requestId})`)
18+
19+
try {
20+
const authError = verifyCronAuth(request, 'Inactivity alert polling')
21+
if (authError) {
22+
return authError
23+
}
24+
25+
const locked = await acquireLock(LOCK_KEY, requestId, LOCK_TTL_SECONDS)
26+
27+
if (!locked) {
28+
return NextResponse.json(
29+
{
30+
success: true,
31+
message: 'Polling already in progress – skipped',
32+
requestId,
33+
status: 'skip',
34+
},
35+
{ status: 202 }
36+
)
37+
}
38+
39+
const results = await pollInactivityAlerts()
40+
41+
return NextResponse.json({
42+
success: true,
43+
message: 'Inactivity alert polling completed',
44+
requestId,
45+
status: 'completed',
46+
...results,
47+
})
48+
} catch (error) {
49+
logger.error(`Error during inactivity alert polling (${requestId}):`, error)
50+
return NextResponse.json(
51+
{
52+
success: false,
53+
message: 'Inactivity alert polling failed',
54+
error: error instanceof Error ? error.message : 'Unknown error',
55+
requestId,
56+
},
57+
{ status: 500 }
58+
)
59+
} finally {
60+
await releaseLock(LOCK_KEY).catch(() => {})
61+
}
62+
}

0 commit comments

Comments
 (0)