Skip to content

Commit 4899c28

Browse files
authored
fix(notifications): consolidate notification utils, update email styling (#2822)
* fix(notifications): consolidate notification utils, update email styling * fixed duplicate types
1 parent 2cee30f commit 4899c28

File tree

13 files changed

+302
-190
lines changed

13 files changed

+302
-190
lines changed

apps/sim/app/api/emails/preview/route.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
renderPlanWelcomeEmail,
1616
renderUsageThresholdEmail,
1717
renderWelcomeEmail,
18+
renderWorkflowNotificationEmail,
1819
renderWorkspaceInvitationEmail,
1920
} from '@/components/emails'
2021

@@ -108,6 +109,51 @@ const emailTemplates = {
108109
message:
109110
'I have 10 years of experience building scalable distributed systems. Most recently, I led a team at a Series B startup where we scaled from 100K to 10M users.',
110111
}),
112+
113+
// Notification emails
114+
'workflow-notification-success': () =>
115+
renderWorkflowNotificationEmail({
116+
workflowName: 'Customer Onboarding Flow',
117+
status: 'success',
118+
trigger: 'api',
119+
duration: '2.3s',
120+
cost: '$0.0042',
121+
logUrl: 'https://sim.ai/workspace/ws_123/logs?search=exec_abc123',
122+
}),
123+
'workflow-notification-error': () =>
124+
renderWorkflowNotificationEmail({
125+
workflowName: 'Customer Onboarding Flow',
126+
status: 'error',
127+
trigger: 'webhook',
128+
duration: '1.1s',
129+
cost: '$0.0021',
130+
logUrl: 'https://sim.ai/workspace/ws_123/logs?search=exec_abc123',
131+
}),
132+
'workflow-notification-alert': () =>
133+
renderWorkflowNotificationEmail({
134+
workflowName: 'Customer Onboarding Flow',
135+
status: 'error',
136+
trigger: 'schedule',
137+
duration: '45.2s',
138+
cost: '$0.0156',
139+
logUrl: 'https://sim.ai/workspace/ws_123/logs?search=exec_abc123',
140+
alertReason: '3 consecutive failures detected',
141+
}),
142+
'workflow-notification-full': () =>
143+
renderWorkflowNotificationEmail({
144+
workflowName: 'Data Processing Pipeline',
145+
status: 'success',
146+
trigger: 'api',
147+
duration: '12.5s',
148+
cost: '$0.0234',
149+
logUrl: 'https://sim.ai/workspace/ws_123/logs?search=exec_abc123',
150+
finalOutput: { processed: 150, skipped: 3, status: 'completed' },
151+
rateLimits: {
152+
sync: { requestsPerMinute: 60, remaining: 45 },
153+
async: { requestsPerMinute: 120, remaining: 98 },
154+
},
155+
usageData: { currentPeriodCost: 12.45, limit: 50, percentUsed: 24.9 },
156+
}),
111157
} as const
112158

113159
type EmailTemplate = keyof typeof emailTemplates
@@ -131,6 +177,12 @@ export async function GET(request: NextRequest) {
131177
'payment-failed',
132178
],
133179
Careers: ['careers-confirmation', 'careers-submission'],
180+
Notifications: [
181+
'workflow-notification-success',
182+
'workflow-notification-error',
183+
'workflow-notification-alert',
184+
'workflow-notification-full',
185+
],
134186
}
135187

136188
const categoryHtml = Object.entries(categories)

apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ const updateNotificationSchema = z
8080
levelFilter: levelFilterSchema.optional(),
8181
triggerFilter: triggerFilterSchema.optional(),
8282
includeFinalOutput: z.boolean().optional(),
83-
includeTraceSpans: z.boolean().optional(),
8483
includeRateLimits: z.boolean().optional(),
8584
includeUsageData: z.boolean().optional(),
8685
alertConfig: alertConfigSchema.optional(),
@@ -147,7 +146,6 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
147146
levelFilter: subscription.levelFilter,
148147
triggerFilter: subscription.triggerFilter,
149148
includeFinalOutput: subscription.includeFinalOutput,
150-
includeTraceSpans: subscription.includeTraceSpans,
151149
includeRateLimits: subscription.includeRateLimits,
152150
includeUsageData: subscription.includeUsageData,
153151
webhookConfig: subscription.webhookConfig,
@@ -222,7 +220,6 @@ export async function PUT(request: NextRequest, { params }: RouteParams) {
222220
if (data.triggerFilter !== undefined) updateData.triggerFilter = data.triggerFilter
223221
if (data.includeFinalOutput !== undefined)
224222
updateData.includeFinalOutput = data.includeFinalOutput
225-
if (data.includeTraceSpans !== undefined) updateData.includeTraceSpans = data.includeTraceSpans
226223
if (data.includeRateLimits !== undefined) updateData.includeRateLimits = data.includeRateLimits
227224
if (data.includeUsageData !== undefined) updateData.includeUsageData = data.includeUsageData
228225
if (data.alertConfig !== undefined) updateData.alertConfig = data.alertConfig
@@ -260,7 +257,6 @@ export async function PUT(request: NextRequest, { params }: RouteParams) {
260257
levelFilter: subscription.levelFilter,
261258
triggerFilter: subscription.triggerFilter,
262259
includeFinalOutput: subscription.includeFinalOutput,
263-
includeTraceSpans: subscription.includeTraceSpans,
264260
includeRateLimits: subscription.includeRateLimits,
265261
includeUsageData: subscription.includeUsageData,
266262
webhookConfig: subscription.webhookConfig,

apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/test/route.ts

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@ import { createLogger } from '@sim/logger'
55
import { and, eq } from 'drizzle-orm'
66
import { type NextRequest, NextResponse } from 'next/server'
77
import { v4 as uuidv4 } from 'uuid'
8+
import {
9+
type EmailRateLimitsData,
10+
type EmailUsageData,
11+
renderWorkflowNotificationEmail,
12+
} from '@/components/emails'
813
import { getSession } from '@/lib/auth'
914
import { decryptSecret } from '@/lib/core/security/encryption'
15+
import { getBaseUrl } from '@/lib/core/utils/urls'
1016
import { sendEmail } from '@/lib/messaging/email/mailer'
1117
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
1218

@@ -67,29 +73,23 @@ function buildTestPayload(subscription: typeof workspaceNotificationSubscription
6773
data.finalOutput = { message: 'This is a test notification', test: true }
6874
}
6975

70-
if (subscription.includeTraceSpans) {
71-
data.traceSpans = [
72-
{
73-
id: 'span_test_1',
74-
name: 'Test Block',
75-
type: 'block',
76-
status: 'success',
77-
startTime: new Date(timestamp - 5000).toISOString(),
78-
endTime: new Date(timestamp).toISOString(),
79-
duration: 5000,
80-
},
81-
]
82-
}
83-
8476
if (subscription.includeRateLimits) {
8577
data.rateLimits = {
86-
sync: { limit: 150, remaining: 45, resetAt: new Date(timestamp + 60000).toISOString() },
87-
async: { limit: 1000, remaining: 50, resetAt: new Date(timestamp + 60000).toISOString() },
78+
sync: {
79+
requestsPerMinute: 150,
80+
remaining: 45,
81+
resetAt: new Date(timestamp + 60000).toISOString(),
82+
},
83+
async: {
84+
requestsPerMinute: 1000,
85+
remaining: 50,
86+
resetAt: new Date(timestamp + 60000).toISOString(),
87+
},
8888
}
8989
}
9090

9191
if (subscription.includeUsageData) {
92-
data.usage = { currentPeriodCost: 2.45, limit: 20, plan: 'pro', isExceeded: false }
92+
data.usage = { currentPeriodCost: 2.45, limit: 20, percentUsed: 12.25, isExceeded: false }
9393
}
9494

9595
return { payload, timestamp }
@@ -157,23 +157,26 @@ async function testEmail(subscription: typeof workspaceNotificationSubscription.
157157

158158
const { payload } = buildTestPayload(subscription)
159159
const data = (payload as Record<string, unknown>).data as Record<string, unknown>
160+
const baseUrl = getBaseUrl()
161+
const logUrl = `${baseUrl}/workspace/${subscription.workspaceId}/logs`
162+
163+
const html = await renderWorkflowNotificationEmail({
164+
workflowName: data.workflowName as string,
165+
status: data.status as 'success' | 'error',
166+
trigger: data.trigger as string,
167+
duration: `${data.totalDurationMs}ms`,
168+
cost: `$${(((data.cost as Record<string, unknown>)?.total as number) || 0).toFixed(4)}`,
169+
logUrl,
170+
finalOutput: data.finalOutput,
171+
rateLimits: data.rateLimits as EmailRateLimitsData | undefined,
172+
usageData: data.usage as EmailUsageData | undefined,
173+
})
160174

161175
const result = await sendEmail({
162176
to: subscription.emailRecipients,
163177
subject: `[Test] Workflow Execution: ${data.workflowName}`,
164-
text: `This is a test notification from Sim Studio.\n\nWorkflow: ${data.workflowName}\nStatus: ${data.status}\nDuration: ${data.totalDurationMs}ms\n\nThis notification is configured for workspace notifications.`,
165-
html: `
166-
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
167-
<h2 style="color: #7F2FFF;">Test Notification</h2>
168-
<p>This is a test notification from Sim Studio.</p>
169-
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
170-
<tr><td style="padding: 8px; border: 1px solid #eee;"><strong>Workflow</strong></td><td style="padding: 8px; border: 1px solid #eee;">${data.workflowName}</td></tr>
171-
<tr><td style="padding: 8px; border: 1px solid #eee;"><strong>Status</strong></td><td style="padding: 8px; border: 1px solid #eee;">${data.status}</td></tr>
172-
<tr><td style="padding: 8px; border: 1px solid #eee;"><strong>Duration</strong></td><td style="padding: 8px; border: 1px solid #eee;">${data.totalDurationMs}ms</td></tr>
173-
</table>
174-
<p style="color: #666; font-size: 12px;">This notification is configured for workspace notifications.</p>
175-
</div>
176-
`,
178+
html,
179+
text: `This is a test notification from Sim.\n\nWorkflow: ${data.workflowName}\nStatus: ${data.status}\nDuration: ${data.totalDurationMs}ms\n\nView Log: ${logUrl}\n\nThis notification is configured for workspace notifications.`,
177180
emailType: 'notifications',
178181
})
179182

@@ -227,7 +230,7 @@ async function testSlack(
227230
elements: [
228231
{
229232
type: 'mrkdwn',
230-
text: 'This is a test notification from Sim Studio workspace notifications.',
233+
text: 'This is a test notification from Sim workspace notifications.',
231234
},
232235
],
233236
},

apps/sim/app/api/workspaces/[id]/notifications/route.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ const createNotificationSchema = z
8383
levelFilter: levelFilterSchema.default(['info', 'error']),
8484
triggerFilter: triggerFilterSchema.default([...CORE_TRIGGER_TYPES]),
8585
includeFinalOutput: z.boolean().default(false),
86-
includeTraceSpans: z.boolean().default(false),
8786
includeRateLimits: z.boolean().default(false),
8887
includeUsageData: z.boolean().default(false),
8988
alertConfig: alertConfigSchema.optional(),
@@ -138,7 +137,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
138137
levelFilter: workspaceNotificationSubscription.levelFilter,
139138
triggerFilter: workspaceNotificationSubscription.triggerFilter,
140139
includeFinalOutput: workspaceNotificationSubscription.includeFinalOutput,
141-
includeTraceSpans: workspaceNotificationSubscription.includeTraceSpans,
142140
includeRateLimits: workspaceNotificationSubscription.includeRateLimits,
143141
includeUsageData: workspaceNotificationSubscription.includeUsageData,
144142
webhookConfig: workspaceNotificationSubscription.webhookConfig,
@@ -240,7 +238,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
240238
levelFilter: data.levelFilter,
241239
triggerFilter: data.triggerFilter,
242240
includeFinalOutput: data.includeFinalOutput,
243-
includeTraceSpans: data.includeTraceSpans,
244241
includeRateLimits: data.includeRateLimits,
245242
includeUsageData: data.includeUsageData,
246243
alertConfig: data.alertConfig || null,
@@ -266,7 +263,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
266263
levelFilter: subscription.levelFilter,
267264
triggerFilter: subscription.triggerFilter,
268265
includeFinalOutput: subscription.includeFinalOutput,
269-
includeTraceSpans: subscription.includeTraceSpans,
270266
includeRateLimits: subscription.includeRateLimits,
271267
includeUsageData: subscription.includeUsageData,
272268
webhookConfig: subscription.webhookConfig,

apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ export function NotificationSettings({
136136
levelFilter: ['info', 'error'] as LogLevel[],
137137
triggerFilter: [...CORE_TRIGGER_TYPES] as CoreTriggerType[],
138138
includeFinalOutput: false,
139-
includeTraceSpans: false,
140139
includeRateLimits: false,
141140
includeUsageData: false,
142141
webhookUrl: '',
@@ -203,7 +202,6 @@ export function NotificationSettings({
203202
levelFilter: ['info', 'error'],
204203
triggerFilter: [...CORE_TRIGGER_TYPES],
205204
includeFinalOutput: false,
206-
includeTraceSpans: false,
207205
includeRateLimits: false,
208206
includeUsageData: false,
209207
webhookUrl: '',
@@ -422,7 +420,6 @@ export function NotificationSettings({
422420
levelFilter: formData.levelFilter,
423421
triggerFilter: formData.triggerFilter,
424422
includeFinalOutput: formData.includeFinalOutput,
425-
includeTraceSpans: formData.includeTraceSpans,
426423
includeRateLimits: formData.includeRateLimits,
427424
includeUsageData: formData.includeUsageData,
428425
alertConfig,
@@ -474,7 +471,6 @@ export function NotificationSettings({
474471
levelFilter: subscription.levelFilter as LogLevel[],
475472
triggerFilter: subscription.triggerFilter as CoreTriggerType[],
476473
includeFinalOutput: subscription.includeFinalOutput,
477-
includeTraceSpans: subscription.includeTraceSpans,
478474
includeRateLimits: subscription.includeRateLimits,
479475
includeUsageData: subscription.includeUsageData,
480476
webhookUrl: subscription.webhookConfig?.url || '',
@@ -830,15 +826,13 @@ export function NotificationSettings({
830826
<Combobox
831827
options={[
832828
{ label: 'Final Output', value: 'includeFinalOutput' },
833-
{ label: 'Trace Spans', value: 'includeTraceSpans' },
834829
{ label: 'Rate Limits', value: 'includeRateLimits' },
835830
{ label: 'Usage Data', value: 'includeUsageData' },
836831
]}
837832
multiSelect
838833
multiSelectValues={
839834
[
840835
formData.includeFinalOutput && 'includeFinalOutput',
841-
formData.includeTraceSpans && 'includeTraceSpans',
842836
formData.includeRateLimits && 'includeRateLimits',
843837
formData.includeUsageData && 'includeUsageData',
844838
].filter(Boolean) as string[]
@@ -847,7 +841,6 @@ export function NotificationSettings({
847841
setFormData({
848842
...formData,
849843
includeFinalOutput: values.includes('includeFinalOutput'),
850-
includeTraceSpans: values.includes('includeTraceSpans'),
851844
includeRateLimits: values.includes('includeRateLimits'),
852845
includeUsageData: values.includes('includeUsageData'),
853846
})
@@ -856,13 +849,11 @@ export function NotificationSettings({
856849
overlayContent={(() => {
857850
const labels: Record<string, string> = {
858851
includeFinalOutput: 'Final Output',
859-
includeTraceSpans: 'Trace Spans',
860852
includeRateLimits: 'Rate Limits',
861853
includeUsageData: 'Usage Data',
862854
}
863855
const selected = [
864856
formData.includeFinalOutput && 'includeFinalOutput',
865-
formData.includeTraceSpans && 'includeTraceSpans',
866857
formData.includeRateLimits && 'includeRateLimits',
867858
formData.includeUsageData && 'includeUsageData',
868859
].filter(Boolean) as string[]

0 commit comments

Comments
 (0)