diff --git a/apps/api/src/trigger/browser-automation/run-browser-automation.ts b/apps/api/src/trigger/browser-automation/run-browser-automation.ts index e463917b4..d5b12c616 100644 --- a/apps/api/src/trigger/browser-automation/run-browser-automation.ts +++ b/apps/api/src/trigger/browser-automation/run-browser-automation.ts @@ -46,6 +46,7 @@ async function sendTaskStatusChangeEmails(params: { where: { organizationId, deactivated: false, + user: { role: { not: 'admin' } }, }, select: { role: true, @@ -113,6 +114,7 @@ async function sendTaskStatusChangeEmails(params: { db, recipient.email, 'taskAssignments', + organizationId, ); if (isUnsubscribed) { diff --git a/apps/api/src/trigger/integration-platform/run-task-integration-checks.ts b/apps/api/src/trigger/integration-platform/run-task-integration-checks.ts index 08b9d7be1..36e1c7f50 100644 --- a/apps/api/src/trigger/integration-platform/run-task-integration-checks.ts +++ b/apps/api/src/trigger/integration-platform/run-task-integration-checks.ts @@ -44,6 +44,7 @@ async function sendTaskStatusChangeEmails(params: { where: { organizationId, deactivated: false, + user: { role: { not: 'admin' } }, }, select: { role: true, @@ -111,6 +112,7 @@ async function sendTaskStatusChangeEmails(params: { db, recipient.email, 'taskAssignments', + organizationId, ); if (isUnsubscribed) { diff --git a/packages/email/lib/check-unsubscribe.ts b/packages/email/lib/check-unsubscribe.ts index 74437aade..eff4fbf54 100644 --- a/packages/email/lib/check-unsubscribe.ts +++ b/packages/email/lib/check-unsubscribe.ts @@ -117,26 +117,9 @@ export async function isUserUnsubscribed( return false; } - // Platform admins only receive notifications for organizations they own + // Platform admins never receive email notifications if (user.role === 'admin') { - if (!organizationId || !db.member) { - return true; // No org context — block notifications - } - const adminMemberRecords = await db.member.findMany({ - where: { - organizationId, - user: { email }, - deactivated: false, - }, - select: { role: true }, - }); - const adminRoles = adminMemberRecords.flatMap((m) => - m.role.split(',').map((r) => r.trim()), - ); - if (!adminRoles.includes('owner')) { - return true; // Not an owner in this org — block notifications - } - // Platform admin IS an owner — fall through to normal notification logic + return true; } // If legacy all-or-nothing flag is set, user is unsubscribed from everything diff --git a/packages/integration-platform/src/manifests/google-workspace/checks/employee-access.ts b/packages/integration-platform/src/manifests/google-workspace/checks/employee-access.ts index b04a54f8c..4a771ab4b 100644 --- a/packages/integration-platform/src/manifests/google-workspace/checks/employee-access.ts +++ b/packages/integration-platform/src/manifests/google-workspace/checks/employee-access.ts @@ -127,6 +127,18 @@ export const employeeAccessCheck: IntegrationCheck = { ctx.log(`Fetched ${allUsers.length} total users`); + if (userFilterConfig.targetOrgUnits?.length) { + const ouCounts = new Map(); + for (const user of allUsers) { + const ou = user.orgUnitPath ?? '/'; + ouCounts.set(ou, (ouCounts.get(ou) ?? 0) + 1); + } + ctx.log( + `Filtering to OUs: ${userFilterConfig.targetOrgUnits.join(', ')}. ` + + `User OUs: ${[...ouCounts.entries()].map(([ou, count]) => `${ou} (${count})`).join(', ')}`, + ); + } + // Same rules as 2FA check and employee sync (sync.controller.ts) const activeUsers = filterGoogleWorkspaceUsersForChecks(allUsers, userFilterConfig); diff --git a/packages/integration-platform/src/manifests/google-workspace/checks/two-factor-auth.ts b/packages/integration-platform/src/manifests/google-workspace/checks/two-factor-auth.ts index 3493f6dc3..bf4f16932 100644 --- a/packages/integration-platform/src/manifests/google-workspace/checks/two-factor-auth.ts +++ b/packages/integration-platform/src/manifests/google-workspace/checks/two-factor-auth.ts @@ -51,6 +51,18 @@ export const twoFactorAuthCheck: IntegrationCheck = { ctx.log(`Fetched ${allUsers.length} total users`); + if (userFilterConfig.targetOrgUnits?.length) { + const ouCounts = new Map(); + for (const user of allUsers) { + const ou = user.orgUnitPath ?? '/'; + ouCounts.set(ou, (ouCounts.get(ou) ?? 0) + 1); + } + ctx.log( + `Filtering to OUs: ${userFilterConfig.targetOrgUnits.join(', ')}. ` + + `User OUs: ${[...ouCounts.entries()].map(([ou, count]) => `${ou} (${count})`).join(', ')}`, + ); + } + // Org units + sync email filter — same rules as employee sync (sync.controller.ts) const usersToCheck = filterGoogleWorkspaceUsersForChecks(allUsers, userFilterConfig); diff --git a/packages/integration-platform/src/manifests/google-workspace/variables.ts b/packages/integration-platform/src/manifests/google-workspace/variables.ts index ba453e318..840c8a9a7 100644 --- a/packages/integration-platform/src/manifests/google-workspace/variables.ts +++ b/packages/integration-platform/src/manifests/google-workspace/variables.ts @@ -17,14 +17,19 @@ export const targetOrgUnitsVariable: CheckVariable = { '/admin/directory/v1/customer/my_customer/orgunits?type=all', ); + const rootOption = { value: '/', label: '/ (Root)' }; + if (!response.organizationUnits) { - return [{ value: '/', label: '/ (Root)' }]; + return [rootOption]; } - return response.organizationUnits.map((ou) => ({ - value: ou.orgUnitPath, - label: `${ou.orgUnitPath} (${ou.name})`, - })); + return [ + rootOption, + ...response.organizationUnits.map((ou) => ({ + value: ou.orgUnitPath, + label: `${ou.orgUnitPath} (${ou.name})`, + })), + ]; } catch { return [{ value: '/', label: '/ (Root)' }]; }