Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
126a42c
feat(notification): slack, email, webhook notifications from logs
icecrasher321 Dec 2, 2025
5928b92
retain search params for filters to link in notification
icecrasher321 Dec 2, 2025
8e6c509
add alerting rules
icecrasher321 Dec 2, 2025
5d4bcdc
update selector
icecrasher321 Dec 2, 2025
3b8fad2
fix lint
icecrasher321 Dec 2, 2025
ed82ded
add limits on num of emails and notification triggers per workspace
icecrasher321 Dec 2, 2025
222bd4e
address greptile comments
icecrasher321 Dec 2, 2025
d0fdb86
add search to combobox
icecrasher321 Dec 2, 2025
4e14862
move notifications to react query
icecrasher321 Dec 2, 2025
0e58fae
fix lint
icecrasher321 Dec 2, 2025
a004934
fix email formatting
icecrasher321 Dec 2, 2025
6cd5707
add more alert types
icecrasher321 Dec 2, 2025
f0b525f
Merge branch 'staging' into feat/notifications-workflow-execs
icecrasher321 Dec 2, 2025
cfc1954
fix imports
icecrasher321 Dec 2, 2025
d63bb9e
fix test route
icecrasher321 Dec 2, 2025
cc5a165
Merge branch 'staging' into feat/notifications-workflow-execs
icecrasher321 Dec 4, 2025
30b0391
use emcn componentfor modal
icecrasher321 Dec 4, 2025
6d1ff0c
refactor: consolidate notification config fields into jsonb objects
icecrasher321 Dec 4, 2025
6c8019f
regen migration
icecrasher321 Dec 4, 2025
f09f2dc
fix delete notif modal ui
icecrasher321 Dec 5, 2025
909b349
make them multiselect dropdowns
icecrasher321 Dec 5, 2025
e35517f
update tag styling
icecrasher321 Dec 5, 2025
747e820
combobox font size with multiselect tags'
icecrasher321 Dec 5, 2025
64305ab
Merge staging into feat/notifications-workflow-execs
icecrasher321 Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 63 additions & 17 deletions apps/docs/content/docs/en/execution/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -240,32 +240,78 @@ Retrieve execution details including the workflow state snapshot.
</Tab>
</Tabs>

## Webhook Subscriptions
## Notifications

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

### Configuration

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.
Configure notifications from the Logs page by clicking the menu button and selecting "Configure Notifications".

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

**Available Configuration Options:**
**Workflow Selection:**
- Select specific workflows to monitor
- Or choose "All Workflows" to include current and future workflows

**Filtering Options:**
- `levelFilter`: Log levels to receive (`info`, `error`)
- `triggerFilter`: Trigger types to receive (`api`, `webhook`, `schedule`, `manual`, `chat`)

**Optional Data:**
- `includeFinalOutput`: Include the workflow's final output
- `includeTraceSpans`: Include detailed execution trace spans
- `includeRateLimits`: Include rate limit information (sync/async limits and remaining)
- `includeUsageData`: Include billing period usage and limits

### Alert Rules

Instead of receiving notifications for every execution, configure alert rules to be notified only when issues are detected:

**Consecutive Failures**
- Alert after X consecutive failed executions (e.g., 3 failures in a row)
- Resets when an execution succeeds

**Failure Rate**
- Alert when failure rate exceeds X% over the last Y hours
- Requires minimum 5 executions in the window
- Only triggers after the full time window has elapsed

**Latency Threshold**
- Alert when any execution takes longer than X seconds
- Useful for catching slow or hanging workflows

**Latency Spike**
- Alert when execution is X% slower than the average
- Compares against the average duration over the configured time window
- Requires minimum 5 executions to establish baseline

**Cost Threshold**
- Alert when a single execution costs more than $X
- Useful for catching expensive LLM calls

**No Activity**
- Alert when no executions occur within X hours
- Useful for monitoring scheduled workflows that should run regularly

**Error Count**
- Alert when error count exceeds X within a time window
- Tracks total errors, not consecutive

All alert types include a 1-hour cooldown to prevent notification spam.

### Webhook Configuration

For webhooks, additional options are available:
- `url`: Your webhook endpoint URL
- `secret`: Optional secret for HMAC signature verification
- `includeFinalOutput`: Include the workflow's final output in the payload
- `includeTraceSpans`: Include detailed execution trace spans
- `includeRateLimits`: Include the workflow owner's rate limit information
- `includeUsageData`: Include the workflow owner's usage and billing data
- `levelFilter`: Array of log levels to receive (`info`, `error`)
- `triggerFilter`: Array of trigger types to receive (`api`, `webhook`, `schedule`, `manual`, `chat`)
- `active`: Enable/disable the webhook subscription

### Webhook Payload
### Payload Structure

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

```json
{
Expand Down Expand Up @@ -316,7 +362,7 @@ When a workflow execution completes, Sim sends a POST request to your webhook UR

### Webhook Headers

Each webhook request includes these headers:
Each webhook request includes these headers (webhook channel only):

- `sim-event`: Event type (always `workflow.execution.completed`)
- `sim-timestamp`: Unix timestamp in milliseconds
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/en/execution/logging.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,4 @@ The snapshot provides:

- Learn about [Cost Calculation](/execution/costs) to understand workflow pricing
- Explore the [External API](/execution/api) for programmatic log access
- Set up [Webhook notifications](/execution/api#webhook-subscriptions) for real-time alerts
- Set up [Notifications](/execution/api#notifications) for real-time alerts via webhook, email, or Slack
40 changes: 40 additions & 0 deletions apps/sim/app/api/auth/accounts/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { db } from '@sim/db'
import { account } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console/logger'

const logger = createLogger('AuthAccountsAPI')

export async function GET(request: NextRequest) {
try {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const { searchParams } = new URL(request.url)
const provider = searchParams.get('provider')

const whereConditions = [eq(account.userId, session.user.id)]

if (provider) {
whereConditions.push(eq(account.providerId, provider))
}

const accounts = await db
.select({
id: account.id,
accountId: account.accountId,
providerId: account.providerId,
})
.from(account)
.where(and(...whereConditions))

return NextResponse.json({ accounts })
} catch (error) {
logger.error('Failed to fetch accounts', { error })
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
62 changes: 62 additions & 0 deletions apps/sim/app/api/notifications/poll/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { nanoid } from 'nanoid'
import { type NextRequest, NextResponse } from 'next/server'
import { verifyCronAuth } from '@/lib/auth/internal'
import { acquireLock, releaseLock } from '@/lib/core/config/redis'
import { createLogger } from '@/lib/logs/console/logger'
import { pollInactivityAlerts } from '@/lib/notifications/inactivity-polling'

const logger = createLogger('InactivityAlertPoll')

export const maxDuration = 120

const LOCK_KEY = 'inactivity-alert-polling-lock'
const LOCK_TTL_SECONDS = 120

export async function GET(request: NextRequest) {
const requestId = nanoid()
logger.info(`Inactivity alert polling triggered (${requestId})`)

try {
const authError = verifyCronAuth(request, 'Inactivity alert polling')
if (authError) {
return authError
}

const locked = await acquireLock(LOCK_KEY, requestId, LOCK_TTL_SECONDS)

if (!locked) {
return NextResponse.json(
{
success: true,
message: 'Polling already in progress – skipped',
requestId,
status: 'skip',
},
{ status: 202 }
)
}

const results = await pollInactivityAlerts()

return NextResponse.json({
success: true,
message: 'Inactivity alert polling completed',
requestId,
status: 'completed',
...results,
})
} catch (error) {
logger.error(`Error during inactivity alert polling (${requestId}):`, error)
return NextResponse.json(
{
success: false,
message: 'Inactivity alert polling failed',
error: error instanceof Error ? error.message : 'Unknown error',
requestId,
},
{ status: 500 }
)
} finally {
await releaseLock(LOCK_KEY).catch(() => {})
}
}
Loading